From 9d30ea1a179a9d5fba1da51a6de203863bd38d21 Mon Sep 17 00:00:00 2001 From: alandefreitas Date: Sat, 26 Oct 2024 02:28:02 -0300 Subject: [PATCH] refactor: lazy object mapping traits fix #696 --- include/mrdocs/Dom/Object.hpp | 74 +-- include/mrdocs/Dom/Object.ipp | 2 +- include/mrdocs/Dom/Value.hpp | 87 ++- .../{DomMetadata.hpp => DomCorpus.hpp} | 18 +- include/mrdocs/Metadata/Symbols.hpp | 5 +- include/mrdocs/mrdocs.natvis | 1 - src/lib/Dom/LazyObject.hpp | 370 +++++++++++ src/lib/Dom/Object.cpp | 74 --- src/lib/Gen/adoc/AdocCorpus.cpp | 192 ++---- src/lib/Gen/adoc/AdocCorpus.hpp | 43 +- src/lib/Gen/adoc/AdocGenerator.cpp | 3 +- src/lib/Gen/adoc/Builder.cpp | 2 +- src/lib/Gen/adoc/Builder.hpp | 6 +- src/lib/Gen/html/Builder.cpp | 2 +- src/lib/Gen/html/Builder.hpp | 2 +- src/lib/Gen/html/HTMLCorpus.cpp | 98 +-- src/lib/Gen/html/HTMLCorpus.hpp | 19 +- src/lib/Gen/html/HTMLGenerator.cpp | 2 +- .../{DomMetadata.cpp => DomCorpus.cpp} | 574 +++++++++--------- src/test/lib/Dom/LazyObject.cpp | 255 ++++++++ 20 files changed, 1148 insertions(+), 681 deletions(-) rename include/mrdocs/Metadata/{DomMetadata.hpp => DomCorpus.hpp} (81%) create mode 100644 src/lib/Dom/LazyObject.hpp rename src/lib/Metadata/{DomMetadata.cpp => DomCorpus.cpp} (62%) create mode 100644 src/test/lib/Dom/LazyObject.cpp diff --git a/include/mrdocs/Dom/Object.hpp b/include/mrdocs/Dom/Object.hpp index 1c9081ec9..2d8bc818f 100644 --- a/include/mrdocs/Dom/Object.hpp +++ b/include/mrdocs/Dom/Object.hpp @@ -4,6 +4,7 @@ // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception // // Copyright (c) 2023 Vinnie Falco (vinnie.falco@gmail.com) +// Copyright (c) 2024 Alan de Freitas (alandefreitas@gmail.com) // // Official repository: https://github.com/cppalliance/mrdocs // @@ -268,7 +269,7 @@ class MRDOCS_DECL template requires std::invocable && - detail::isExpected> + mrdocs::detail::isExpected> Expected::error_type> visit(F&& fn) const; @@ -414,77 +415,6 @@ class MRDOCS_DECL storage_type entries_; }; -//------------------------------------------------ -// -// LazyObjectImpl -// -//------------------------------------------------ - -/** Abstract lazy object interface. - - This interface is used to define objects - that are constructed on demand. - - The subclass must override the `construct` - function to return the constructed object. - It will typically also store whatever - data is necessary to construct this object. - - When any of the object properties are accessed - for the first time, the object is constructed. - This can happen via any of the public functions, - such as `get`, `set`, `size`, `exists`, or `visit`. - - The underlying object storage is only - initialized when the first property is - set or accessed. In practice, it means - the object is never initialized if it's - not used in a template. - - When the object is initialized, the - -*/ -class MRDOCS_DECL - LazyObjectImpl : public ObjectImpl -{ -#ifdef __cpp_lib_atomic_shared_ptr - std::atomic> mutable sp_; -#else - std::shared_ptr mutable sp_; -#endif - - using impl_type = Object::impl_type; - - /* Return the constructed object. - - This function is invoked by all public - functions that access the object properties. - - When invoked for the first time, the object - is constructed and stored in the shared - pointer. - - Further invocations return a reference - to the existing value in the shared pointer. - */ - ObjectImpl& obj() const; - -protected: - /** Return the constructed object. - - Subclasses override this. - The function is invoked just in time. - */ - virtual Object construct() const = 0; - -public: - std::size_t size() const override; - Value get(std::string_view key) const override; - void set(String key, Value value) override; - bool visit(std::function) const override; - bool exists(std::string_view key) const override; -}; - } // dom } // mrdocs } // clang diff --git a/include/mrdocs/Dom/Object.ipp b/include/mrdocs/Dom/Object.ipp index 4d131553f..f3c332eca 100644 --- a/include/mrdocs/Dom/Object.ipp +++ b/include/mrdocs/Dom/Object.ipp @@ -124,7 +124,7 @@ void Object::visit(F&& fn) const template requires std::invocable && - detail::isExpected> + mrdocs::detail::isExpected> Expected::error_type> Object::visit(F&& fn) const { diff --git a/include/mrdocs/Dom/Value.hpp b/include/mrdocs/Dom/Value.hpp index bf4bff4b7..bd0f793c4 100644 --- a/include/mrdocs/Dom/Value.hpp +++ b/include/mrdocs/Dom/Value.hpp @@ -54,6 +54,36 @@ safeString(dom::Value const& str); namespace dom { +/** Mapping traits to convert types into dom::Object. + + This class should be specialized by any type that needs to be converted + to/from a @ref dom::Object. For example: + + @code + template<> + struct MappingTraits { + template + static void map(IO &io, MyStruct const& s) + { + io.map("name", s.name); + io.map("size", s.size); + io.map("age", s.age); + } + }; + @endcode + */ +template +struct ToValue { + // Value operator()(T const& o) const; +}; + +/// Concept to determine if @ref ToValue is defined for a type T +template +concept HasToValue = requires(T const& o) +{ + { Value(std::declval>()(o)) } -> std::same_as; +}; + /** A variant container for any kind of Dom value. */ class MRDOCS_DECL @@ -90,13 +120,14 @@ class MRDOCS_DECL template requires - function_traits_convertible_to_value + function_traits_convertible_to_value && + (!HasToValue) Value(F const& f) : Value(Function(f)) {} - template - requires std::is_same_v + template Boolean> + requires (!HasToValue) Value(Boolean const& b) noexcept : kind_(Kind::Boolean) , b_(b) @@ -104,16 +135,23 @@ class MRDOCS_DECL } template - requires (!std::same_as && !std::same_as) + requires + (!std::same_as) && + (!std::same_as) && + (!HasToValue) Value(T v) noexcept : Value(std::int64_t(v)) {} template + requires (!HasToValue) Value(T v) noexcept : Value(std::int64_t(v)) {} Value(char c) noexcept : Value(std::string_view(&c, 1)) {} template - requires std::is_enum_v && (!std::same_as) + requires + std::is_enum_v && + (!std::same_as) && + (!HasToValue) Value(Enum v) noexcept : Value(static_cast>(v)) {} @@ -131,6 +169,7 @@ class MRDOCS_DECL } template StringLike> + requires (!HasToValue) Value(StringLike const& s) : Value(String(s)) { @@ -155,6 +194,12 @@ class MRDOCS_DECL { } + template + Value(T const& t) + : Value(ToValue{}(t)) + { + } + Value& operator=(Value const& other); Value& operator=(Value&& other) noexcept; @@ -425,29 +470,35 @@ class MRDOCS_DECL Value const& lhs, Value const& rhs) noexcept; + /** Compare two values for inequality. + */ + friend + std::strong_ordering + operator<=>( + Value const& lhs, + Value const& rhs) noexcept; + /// @overload template S> - friend auto operator==( - S const& lhs, Value const& rhs) noexcept + friend + auto + operator<=>( + S const& lhs, + Value const& rhs) noexcept { - return Value(lhs) == rhs; + return Value(lhs) <=> rhs; } /// @overload template S> - friend auto operator==( - Value const& lhs, S const& rhs) noexcept - { - return lhs == Value(rhs); - } - - /** Compare two values for inequality. - */ friend - std::strong_ordering + auto operator<=>( Value const& lhs, - Value const& rhs) noexcept; + S const& rhs) noexcept + { + return lhs <=> Value(rhs); + } /** Add or concatenate two values. */ diff --git a/include/mrdocs/Metadata/DomMetadata.hpp b/include/mrdocs/Metadata/DomCorpus.hpp similarity index 81% rename from include/mrdocs/Metadata/DomMetadata.hpp rename to include/mrdocs/Metadata/DomCorpus.hpp index 742b59cc3..f6b3348dc 100644 --- a/include/mrdocs/Metadata/DomMetadata.hpp +++ b/include/mrdocs/Metadata/DomCorpus.hpp @@ -5,12 +5,13 @@ // // Copyright (c) 2023 Vinnie Falco (vinnie.falco@gmail.com) // Copyright (c) 2023 Krystian Stasiowski (sdkrystian@gmail.com) +// Copyright (c) 2024 Alan de Freitas (alandefreitas@gmail.com) // // Official repository: https://github.com/cppalliance/mrdocs // -#ifndef MRDOCS_API_DOM_DOMMETADATA_HPP -#define MRDOCS_API_DOM_DOMMETADATA_HPP +#ifndef MRDOCS_API_DOM_DOMCORPUS_HPP +#define MRDOCS_API_DOM_DOMCORPUS_HPP #include #include @@ -24,9 +25,14 @@ namespace mrdocs { /** Front-end factory for producing Dom nodes. - A @ref Generator subclasses this object and - then uses it to create the Dom nodes used for - rendering in template engines. + This class keeps a reference to the @ref Corpus + of extracted metadata, and provides a mechanism + for constructing DOM nodes representing the metadata. + + A @ref Generator subclasses this object + (e.g. @ref AdocCorpus and @ref HTMLCorpus), + then uses it to create the Dom nodes used + as input for rendering template engines. */ class MRDOCS_DECL DomCorpus @@ -62,7 +68,7 @@ class MRDOCS_DECL */ Corpus const* operator->() const; - /** Construct a Dom object representing the given symbol. + /** Construct a lazy Dom object representing the specified symbol. This function is called internally when a `dom::Object` representing a symbol needs to be constructed because diff --git a/include/mrdocs/Metadata/Symbols.hpp b/include/mrdocs/Metadata/Symbols.hpp index f0a75868b..8000f490e 100644 --- a/include/mrdocs/Metadata/Symbols.hpp +++ b/include/mrdocs/Metadata/Symbols.hpp @@ -31,6 +31,8 @@ namespace mrdocs { */ class SymbolID { + std::uint8_t data_[20]{}; + public: static const SymbolID invalid; static const SymbolID global; @@ -120,9 +122,6 @@ class SymbolID */ bool operator==( const SymbolID& other) const noexcept = default; - -private: - value_type data_[20]{}; }; /** The invalid Symbol ID. diff --git a/include/mrdocs/mrdocs.natvis b/include/mrdocs/mrdocs.natvis index 7b2be5868..6b347961a 100644 --- a/include/mrdocs/mrdocs.natvis +++ b/include/mrdocs/mrdocs.natvis @@ -109,7 +109,6 @@ - [ dom::Object ] diff --git a/src/lib/Dom/LazyObject.hpp b/src/lib/Dom/LazyObject.hpp new file mode 100644 index 000000000..06dff09f7 --- /dev/null +++ b/src/lib/Dom/LazyObject.hpp @@ -0,0 +1,370 @@ +// +// Licensed 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 +// +// Copyright (c) 2024 Alan de Freitas (alandefreitas@gmail.com) +// +// Official repository: https://github.com/cppalliance/mrdocs +// + +#ifndef MRDOCS_API_DOM_MAPPING_TRAITS_HPP +#define MRDOCS_API_DOM_MAPPING_TRAITS_HPP + +#include "mrdocs/Dom.hpp" +#include "mrdocs/Platform.hpp" +#include "mrdocs/Support/Error.hpp" +#include + +namespace clang { +namespace mrdocs { +namespace dom { + +/** Mapping traits to convert types into dom::Object. + + This class should be specialized by any type that needs to be converted + to/from a @ref dom::Object. For example: + + @code + template<> + struct MappingTraits { + template + static void map(IO &io, MyStruct const& s) + { + io.map("name", s.name); + io.map("size", s.size); + io.map("age", s.age); + } + }; + @endcode + */ +template +struct MappingTraits { + // void map(dom::IO &io, T &fields) const; +}; + +namespace detail +{ + /** A class representing an archetypal IO object. + */ + struct MRDOCS_DECL ArchetypalIO + { + template + void + map(std::string_view, T const&) const {} + + template + void + defer(std::string_view, F const&) const {} + }; +} + +/// Concept to determine if @ref MappingTraits is defined for a type T +template +concept HasMappingTraits = requires(detail::ArchetypalIO& io, T const& o) +{ + { std::declval>().map(io, o) } -> std::same_as; +}; + +//------------------------------------------------ +// +// LazyObjectImpl +// +//------------------------------------------------ + +/** Abstract lazy object interface. + + This interface is used to define objects + whose members are evaluated on demand + as they are accessed. + + The subclass must override the `construct` + function to return the constructed object. + It will typically also store whatever + data is necessary to construct this object. + + When any of the object properties are accessed + for the first time, the object is constructed. + This can happen via any of the public functions, + such as `get`, `set`, `size`, `exists`, or `visit`. + + The underlying object storage is only + initialized when the first property is + set or accessed. In practice, it means + the object is never initialized if it's + not used in a template. + + This class is typically useful for + implementing objects that are expensive + and have recursive dependencies, as these + recursive dependencies can also be deferred. + +*/ +template +class LazyObjectImpl : public ObjectImpl +{ + T const* underlying_; + Object overlay_; + [[no_unique_address]] MappingTraits traits_{}; + +public: + explicit + LazyObjectImpl(T const& obj) + requires std::constructible_from> + : underlying_(&obj) + , traits_{} {} + + explicit + LazyObjectImpl(T const& obj, MappingTraits traits) + : underlying_(&obj) + , traits_(std::move(traits)) {} + + ~LazyObjectImpl() override = default; + + /// @copydoc ObjectImpl::type_key + char const* + type_key() const noexcept override + { + return "LazyObject"; + } + + /// @copydoc ObjectImpl::get + Value + get(std::string_view key) const override; + + /// @copydoc ObjectImpl::set + void + set(String key, Value value) override; + + /// @copydoc ObjectImpl::visit + bool + visit(std::function) const override; + + /// @copydoc ObjectImpl::size + std::size_t + size() const override; + + /// @copydoc ObjectImpl::exists + bool + exists(std::string_view key) const override; +}; + +namespace detail +{ + class GetterIO + { + std::string_view key; + Value result; + public: + explicit + GetterIO(std::string_view key) + : key(key) {} + + template + requires std::constructible_from + void + map(std::string_view name, T const& value) + { + if (result.isUndefined() && name == key) + { + this->result = Value(value); + } + } + + template + void + defer(std::string_view name, T const& deferred) + { + using R = std::invoke_result_t; + if constexpr (std::constructible_from) + { + if (result.isUndefined() && name == key) + { + this->result = deferred(); + } + } + } + + Value + get() + { + return std::move(result); + } + }; +} + +template +Value +LazyObjectImpl:: +get(std::string_view key) const +{ + if (overlay_.exists(key)) + { + return overlay_.get(key); + } + detail::GetterIO io{key}; + traits_.map(io, *underlying_); + return io.get(); +} + +template +void +LazyObjectImpl:: +set(String key, Value value) +{ + overlay_.set(std::move(key), std::move(value)); +} + +namespace detail +{ + class VisitIO + { + std::function fn; + Object const& overlay; + bool continueVisiting = true; + public: + explicit + VisitIO(std::function fn, Object const& overlay) + : fn(fn) + , overlay(overlay) {} + + template + void + map(std::string_view name, T const& value) + { + if (continueVisiting && !overlay.exists(name)) + { + continueVisiting = fn(name, Value(value)); + } + } + + template + void + defer(std::string_view name, T const& deferred) + { + if (continueVisiting && !overlay.exists(name)) + { + continueVisiting = fn(name, deferred()); + } + } + + bool + get() + { + return continueVisiting; + } + }; +} + +template +bool +LazyObjectImpl:: +visit(std::function fn) const +{ + detail::VisitIO io{fn, overlay_}; + traits_.map(io, *underlying_); + return io.get() && overlay_.visit(fn); +} + +namespace detail +{ + class SizeIO + { + Object const& overlay; + std::size_t result = 0; + public: + explicit + SizeIO(Object const& overlay) + : overlay(overlay) {} + + template + void + map(std::string_view name, T const&) + { + this->result += !overlay.exists(name); + } + + template + void + defer(std::string_view name, T const&) + { + this->result += !overlay.exists(name); + } + + std::size_t + get() + { + return result + overlay.size(); + } + }; +} + +template +std::size_t +LazyObjectImpl:: +size() const +{ + detail::SizeIO io{overlay_}; + traits_.map(io, *underlying_); + return io.get(); +} + +namespace detail +{ + class ExistsIO + { + std::string_view key; + bool result = false; + public: + explicit + ExistsIO(std::string_view key) + : key(key) {} + + template + void + map(std::string_view name, T const&) + { + if (!result && name == key) + { + this->result = true; + } + } + + template + void + defer(std::string_view name, T const&) + { + if (!result && name == key) + { + this->result = true; + } + } + + bool + get() + { + return result; + } + }; +} + + +template +bool +LazyObjectImpl:: +exists(std::string_view key) const +{ + if (overlay_.exists(key)) + { + return true; + } + detail::ExistsIO io{key}; + traits_.map(io, *underlying_); + return io.get(); +} + +} // dom +} // mrdocs +} // clang + +#endif diff --git a/src/lib/Dom/Object.cpp b/src/lib/Dom/Object.cpp index 1c39cadbd..a3b046e10 100644 --- a/src/lib/Dom/Object.cpp +++ b/src/lib/Dom/Object.cpp @@ -207,80 +207,6 @@ DefaultObjectImpl::exists(std::string_view key) const { return it != entries_.end(); } -//------------------------------------------------ -// -// LazyObjectImpl -// -//------------------------------------------------ - -ObjectImpl& -LazyObjectImpl:: -obj() const -{ -#ifdef __cpp_lib_atomic_shared_ptr - std::shared_ptr impl = sp_.load(); - if (impl) - { - // Already initialized - return *impl; - } - - // Fetch the shared pointer from the factory - std::shared_ptr expected = nullptr; - std::shared_ptr desired = construct().impl(); - MRDOCS_ASSERT(desired); - if (sp_.compare_exchange_strong(expected, std::move(desired))) - { - return *sp_.load(); - } - return *expected; -#else - if (sp_) - { - return *sp_; - } - sp_ = construct().impl(); - MRDOCS_ASSERT(sp_); - return *sp_; -#endif -} - -std::size_t -LazyObjectImpl:: -size() const -{ - return obj().size(); -} - -auto -LazyObjectImpl:: -get(std::string_view key) const -> - Value -{ - return obj().get(key); -} - -void -LazyObjectImpl:: -set(String key, Value value) -{ - return obj().set(std::move(key), std::move(value)); -} - -bool -LazyObjectImpl:: -visit(std::function visitor) const -{ - return obj().visit(std::move(visitor)); -} - -bool -LazyObjectImpl:: -exists(std::string_view key) const -{ - return obj().exists(key); -} - } // dom } // mrdocs } // clang diff --git a/src/lib/Gen/adoc/AdocCorpus.cpp b/src/lib/Gen/adoc/AdocCorpus.cpp index 3b63ec938..40c09e7e2 100644 --- a/src/lib/Gen/adoc/AdocCorpus.cpp +++ b/src/lib/Gen/adoc/AdocCorpus.cpp @@ -85,7 +85,7 @@ class DocVisitor public: DocVisitor( - const AdocCorpus& corpus, + AdocCorpus const& corpus, std::string& dest) noexcept : corpus_(corpus) , dest_(dest) @@ -314,7 +314,7 @@ measureLeftMargin( { if(list.empty()) return 0; - std::size_t n = std::size_t(-1); + auto n = std::size_t(-1); for(auto& text : list) { if(trim(text->string).empty()) @@ -327,7 +327,6 @@ measureLeftMargin( return n; } -static dom::Value domCreate( const doc::Param& I, @@ -345,7 +344,6 @@ domCreate( return dom::Object(std::move(entries)); } -static dom::Value domCreate( const doc::TParam& I, @@ -363,7 +361,6 @@ domCreate( return dom::Object(std::move(entries)); } -static dom::Value domCreate( const doc::Throws& I, @@ -381,7 +378,6 @@ domCreate( return dom::Object(std::move(entries)); } -static dom::Value domCreate( const doc::See& I, @@ -393,7 +389,6 @@ domCreate( return s; } -static dom::Value domCreate( const doc::Precondition& I, @@ -405,7 +400,6 @@ domCreate( return s; } -static dom::Value domCreate( const doc::Postcondition& I, @@ -417,132 +411,15 @@ domCreate( return s; } -//------------------------------------------------ - -class DomJavadoc : public dom::LazyObjectImpl -{ - const AdocCorpus& corpus_; - Javadoc const& jd_; - -public: - DomJavadoc( - const AdocCorpus& corpus, - Javadoc const& jd) noexcept - : corpus_(corpus) - , jd_(jd) - { - } - - template T> - void - maybeEmplace( - storage_type& list, - std::string_view key, - T const& I) const - { - std::string s; - DocVisitor visitor(corpus_, s); - doc::visit(I, visitor); - if(! s.empty()) - list.emplace_back(key, std::move(s)); - } - - template - void - maybeEmplace( - storage_type& list, - std::string_view key, - std::vector const& nodes) const - { - std::string s; - DocVisitor visitor(corpus_, s); - for(auto const& t : nodes) - doc::visit(*t, visitor); - if(! s.empty()) - { - list.emplace_back(key, std::move(s)); - } - } - - template - void - maybeEmplaceArray( - storage_type& list, - std::string_view key, - std::vector const& nodes) const - { - dom::Array::storage_type elements; - elements.reserve(nodes.size()); - for(auto const& elem : nodes) - { - if(! elem) - continue; - elements.emplace_back( - domCreate(*elem, corpus_)); - } - if(elements.empty()) - return; - list.emplace_back(key, dom::newArray< - dom::DefaultArrayImpl>(std::move(elements))); - } - - dom::Object - construct() const override - { - storage_type list; - list.reserve(2); - - auto ov = jd_.makeOverview(*corpus_); - - // brief - if(ov.brief) - maybeEmplace(list, "brief", *ov.brief); - maybeEmplace(list, "description", ov.blocks); - if(ov.returns) - maybeEmplace(list, "returns", *ov.returns); - maybeEmplaceArray(list, "params", ov.params); - maybeEmplaceArray(list, "tparams", ov.tparams); - maybeEmplaceArray(list, "exceptions", ov.exceptions); - maybeEmplaceArray(list, "see", ov.sees); - maybeEmplaceArray(list, "preconditions", ov.preconditions); - maybeEmplaceArray(list, "postconditions", ov.postconditions); - - return dom::Object(std::move(list)); - } -}; - } // (anon) dom::Object AdocCorpus:: construct(Info const& I) const { - // wraps a DomInfo with a lazy object which - // adds additional properties to the wrapped - // object once constructed. - struct AdocInfo : - public dom::LazyObjectImpl - { - Info const& I_; - AdocCorpus const& adocCorpus_; - - public: - AdocInfo( - Info const& I, - AdocCorpus const& adocCorpus) noexcept - : I_(I) - , adocCorpus_(adocCorpus) - { - } - - dom::Object construct() const override - { - auto obj = adocCorpus_.DomCorpus::construct(I_); - obj.set("ref", adocCorpus_.getXref(I_)); - return obj; - } - }; - return dom::newObject(I, *this); + dom::Object obj = this->DomCorpus::construct(I); + obj.set("ref", getXref(I)); + return obj; } std::string @@ -578,7 +455,64 @@ AdocCorpus:: getJavadoc( Javadoc const& jd) const { - return dom::newObject(*this, jd); + dom::Object::storage_type list; + list.reserve(2); + + auto maybeEmplace = [&]( + std::string_view key, + auto const& I) + { + std::string s; + DocVisitor visitor(*this, s); + using T = std::decay_t; + if constexpr (std::derived_from) + { + doc::visit(I, visitor); + } + else if constexpr (std::ranges::range) + { + for(auto const& t : I) + doc::visit(*t, visitor); + } + if(! s.empty()) + { + list.emplace_back(key, std::move(s)); + } + }; + + auto maybeEmplaceArray = [&]( + std::string_view key, + /* std::vector */ auto const& nodes) + { + dom::Array::storage_type elements; + elements.reserve(nodes.size()); + for(auto const& elem : nodes) + { + if(!elem) + continue; + elements.emplace_back( + domCreate(*elem, *this)); + } + if(elements.empty()) + return; + list.emplace_back(key, dom::newArray< + dom::DefaultArrayImpl>(std::move(elements))); + }; + + auto ov = jd.makeOverview(this->getCorpus()); + // brief + if(ov.brief) + maybeEmplace("brief", *ov.brief); + maybeEmplace("description", ov.blocks); + if(ov.returns) + maybeEmplace("returns", *ov.returns); + maybeEmplaceArray("params", ov.params); + maybeEmplaceArray("tparams", ov.tparams); + maybeEmplaceArray("exceptions", ov.exceptions); + maybeEmplaceArray("see", ov.sees); + maybeEmplaceArray("preconditions", ov.preconditions); + maybeEmplaceArray("postconditions", ov.postconditions); + return dom::Object(std::move(list)); } dom::Object diff --git a/src/lib/Gen/adoc/AdocCorpus.hpp b/src/lib/Gen/adoc/AdocCorpus.hpp index a426ba709..341d0264f 100644 --- a/src/lib/Gen/adoc/AdocCorpus.hpp +++ b/src/lib/Gen/adoc/AdocCorpus.hpp @@ -4,6 +4,7 @@ // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception // // Copyright (c) 2023 Vinnie Falco (vinnie.falco@gmail.com) +// Copyright (c) 2024 Alan de Freitas (alandefreitas@gmail.com) // // Official repository: https://github.com/cppalliance/mrdocs // @@ -14,19 +15,34 @@ #include #include "lib/Support/LegibleNames.hpp" #include "Options.hpp" -#include +#include #include namespace clang { namespace mrdocs { namespace adoc { +/** A specialized DomCorpus for generating Adoc nodes. + + This class extends @ref DomCorpus to provide + additional functionality specific to Adoc generation. +*/ class AdocCorpus : public DomCorpus { public: + /** Options for the Adoc corpus. */ Options options; + + /** Legible names for the Adoc corpus. */ LegibleNames names_; + /** Constructor. + + Initializes the AdocCorpus with the given corpus and options. + + @param corpus The base corpus. + @param opts Options for the Adoc corpus. + */ AdocCorpus( Corpus const& corpus, Options&& opts) @@ -36,19 +52,44 @@ class AdocCorpus : public DomCorpus { } + /** Construct a dom::Object from the given Info. + + @param I The Info object to construct from. + @return A dom::Object representing the Info. + */ dom::Object construct(Info const& I) const override; + /** Get the cross-reference for the given Info. + + @param I The Info object to get the cross-reference for. + @return A string representing the cross-reference. + */ std::string getXref(Info const& I) const; + /** Get the cross-reference for the given OverloadSet. + + @param os The OverloadSet to get the cross-reference for. + @return A string representing the cross-reference. + */ std::string getXref(OverloadSet const& os) const; + /** Return a Dom value representing the Javadoc. + + @param jd The Javadoc object to get the Javadoc for. + @return A dom::Value representing the Javadoc. + */ dom::Value getJavadoc( Javadoc const& jd) const override; + /** Return a Dom value representing an overload set. + + @param os The OverloadSet to get the overloads for. + @return A dom::Object representing the overloads. + */ dom::Object getOverloads( OverloadSet const& os) const override; diff --git a/src/lib/Gen/adoc/AdocGenerator.cpp b/src/lib/Gen/adoc/AdocGenerator.cpp index 57cc5681c..5c02bf54d 100644 --- a/src/lib/Gen/adoc/AdocGenerator.cpp +++ b/src/lib/Gen/adoc/AdocGenerator.cpp @@ -15,9 +15,8 @@ #include "MultiPageVisitor.hpp" #include "SinglePageVisitor.hpp" #include "lib/Support/LegibleNames.hpp" -#include +#include #include -#include #include #include diff --git a/src/lib/Gen/adoc/Builder.cpp b/src/lib/Gen/adoc/Builder.cpp index 4c19edf34..18a2f9f5b 100644 --- a/src/lib/Gen/adoc/Builder.cpp +++ b/src/lib/Gen/adoc/Builder.cpp @@ -11,7 +11,7 @@ #include "Builder.hpp" #include "lib/Support/Radix.hpp" #include -#include +#include #include #include #include diff --git a/src/lib/Gen/adoc/Builder.hpp b/src/lib/Gen/adoc/Builder.hpp index da7744799..208cbbef9 100644 --- a/src/lib/Gen/adoc/Builder.hpp +++ b/src/lib/Gen/adoc/Builder.hpp @@ -14,14 +14,12 @@ #include "Options.hpp" #include "AdocCorpus.hpp" #include "lib/Support/Radix.hpp" -#include +#include #include -#include #include +#include #include -#include - namespace clang { namespace mrdocs { namespace adoc { diff --git a/src/lib/Gen/html/Builder.cpp b/src/lib/Gen/html/Builder.cpp index 320fe06ed..2a76d71cb 100644 --- a/src/lib/Gen/html/Builder.cpp +++ b/src/lib/Gen/html/Builder.cpp @@ -10,7 +10,7 @@ #include "Builder.hpp" #include "lib/Support/Radix.hpp" -#include +#include #include #include #include diff --git a/src/lib/Gen/html/Builder.hpp b/src/lib/Gen/html/Builder.hpp index d12c3efb9..0c92c4566 100644 --- a/src/lib/Gen/html/Builder.hpp +++ b/src/lib/Gen/html/Builder.hpp @@ -13,7 +13,7 @@ #include "Options.hpp" #include "lib/Support/Radix.hpp" -#include +#include #include #include #include diff --git a/src/lib/Gen/html/HTMLCorpus.cpp b/src/lib/Gen/html/HTMLCorpus.cpp index 954112752..a2a01aaa4 100644 --- a/src/lib/Gen/html/HTMLCorpus.cpp +++ b/src/lib/Gen/html/HTMLCorpus.cpp @@ -258,82 +258,48 @@ measureLeftMargin( return n; } -//------------------------------------------------ +} // (anon) -class DomJavadoc : public dom::LazyObjectImpl +dom::Value +HTMLCorpus:: +getJavadoc( + Javadoc const& jd) const { - const HTMLCorpus& corpus_; - Javadoc const& jd_; - -public: - DomJavadoc( - const HTMLCorpus& corpus, - Javadoc const& jd) noexcept - : corpus_(corpus) - , jd_(jd) - { - } + dom::Object::storage_type list; - template T> - void - maybeEmplace( - storage_type& list, + auto maybeEmplace = [&]( std::string_view key, - T const& I) const + auto const& I) { std::string s; DocVisitor visitor(s); - doc::visit(I, visitor); - if(! s.empty()) - list.emplace_back(key, std::move(s)); - } - - template - void - maybeEmplace( - storage_type& list, - std::string_view key, - std::vector const& nodes) const - { - std::string s; - DocVisitor visitor(s); - for(auto const& t : nodes) - doc::visit(*t, visitor); - if(! s.empty()) + using T = std::decay_t; + if constexpr (std::derived_from) + { + doc::visit(I, visitor); + } + else if constexpr (std::ranges::range) + { + for(auto const& node : I) + doc::visit(*node, visitor); + } + if(!s.empty()) { list.emplace_back(key, std::move(s)); } - } - - dom::Object - construct() const override - { - storage_type list; - list.reserve(2); - - auto ov = jd_.makeOverview(*corpus_); - - // brief - if(ov.brief) - maybeEmplace(list, "brief", *ov.brief); - maybeEmplace(list, "description", ov.blocks); - if(ov.returns) - maybeEmplace(list, "returns", *ov.returns); - maybeEmplace(list, "params", ov.params); - maybeEmplace(list, "tparams", ov.tparams); - - return dom::Object(std::move(list)); - } -}; - -} // (anon) - -dom::Value -HTMLCorpus:: -getJavadoc( - Javadoc const& jd) const -{ - return dom::newObject(*this, jd); + }; + + list.reserve(2); + auto ov = jd.makeOverview(this->getCorpus()); + // brief + if(ov.brief) + maybeEmplace("brief", *ov.brief); + maybeEmplace("description", ov.blocks); + if(ov.returns) + maybeEmplace("returns", *ov.returns); + maybeEmplace("params", ov.params); + maybeEmplace("tparams", ov.tparams); + return dom::Object(std::move(list)); } } // html diff --git a/src/lib/Gen/html/HTMLCorpus.hpp b/src/lib/Gen/html/HTMLCorpus.hpp index b87ab61d5..bdee006d1 100644 --- a/src/lib/Gen/html/HTMLCorpus.hpp +++ b/src/lib/Gen/html/HTMLCorpus.hpp @@ -11,16 +11,28 @@ #ifndef MRDOCS_LIB_GEN_HTML_HTMLCORPUS_HPP #define MRDOCS_LIB_GEN_HTML_HTMLCORPUS_HPP +#include #include -#include namespace clang { namespace mrdocs { namespace html { +/** A specialized DomCorpus for generating HTML nodes. + + This class extends @ref DomCorpus to provide + additional functionality specific to HTML generation. +*/ class HTMLCorpus : public DomCorpus { public: + /** Constructor. + + Initializes the HTMLCorpus with the given corpus and options. + + @param corpus The base corpus. + @param opts Options for the HTML corpus. + */ explicit HTMLCorpus( Corpus const& corpus) @@ -28,6 +40,11 @@ class HTMLCorpus : public DomCorpus { } + /** Return a Dom value representing the Javadoc. + + @param jd The Javadoc object to get the Javadoc for. + @return A dom::Value representing the Javadoc. + */ dom::Value getJavadoc( Javadoc const& jd) const override; diff --git a/src/lib/Gen/html/HTMLGenerator.cpp b/src/lib/Gen/html/HTMLGenerator.cpp index f67224cd8..40fe09f24 100644 --- a/src/lib/Gen/html/HTMLGenerator.cpp +++ b/src/lib/Gen/html/HTMLGenerator.cpp @@ -15,7 +15,7 @@ #include "MultiPageVisitor.hpp" #include "SinglePageVisitor.hpp" #include "lib/Support/LegibleNames.hpp" -#include +#include #include #include #include diff --git a/src/lib/Metadata/DomMetadata.cpp b/src/lib/Metadata/DomCorpus.cpp similarity index 62% rename from src/lib/Metadata/DomMetadata.cpp rename to src/lib/Metadata/DomCorpus.cpp index afea5c593..d58b134f5 100644 --- a/src/lib/Metadata/DomMetadata.cpp +++ b/src/lib/Metadata/DomCorpus.cpp @@ -11,9 +11,9 @@ #include "lib/Support/Radix.hpp" #include "lib/Support/LegibleNames.hpp" +#include "lib/Dom/LazyObject.hpp" #include -#include -#include +#include #include #include #include @@ -29,22 +29,23 @@ namespace { // //------------------------------------------------ -static dom::Value domCreate( std::unique_ptr const& jd, DomCorpus const& domCorpus) { - if(! jd) + if(!jd) return nullptr; return domCorpus.getJavadoc(*jd); } +/* A Lazy DOM Array type that replaces symbol IDs with their + corresponding DOM objects. +*/ class DomSymbolArray : public dom::ArrayImpl { std::span list_; DomCorpus const& domCorpus_; - //SharedPtr<> ref_; // keep owner of list_ alive public: DomSymbolArray( @@ -62,14 +63,16 @@ class DomSymbolArray : public dom::ArrayImpl dom::Value get(std::size_t i) const override { - MRDOCS_ASSERT(i < list_.size()); - return domCorpus_.get(list_[i]); + if (i < list_.size()) + { + return domCorpus_.get(list_[i]); + } + return dom::Value{}; } }; //------------------------------------------------ -static dom::Object domCreate( OverloadSet const& overloads, @@ -134,7 +137,6 @@ class DomOverloadsArray : public dom::ArrayImpl // //------------------------------------------------ -static dom::Object domCreate(Location const& loc) { @@ -171,7 +173,6 @@ class DomLocationArray : public dom::ArrayImpl } }; -static dom::Object domCreate(SourceInfo const& I) { @@ -563,36 +564,6 @@ class DomBaseArray : public dom::ArrayImpl // //------------------------------------------------ -template -class DomTrancheArray : public dom::ArrayImpl -{ - std::span list_; - std::shared_ptr sp_; - DomCorpus const& domCorpus_; - -public: - DomTrancheArray( - std::span list, - std::shared_ptr const& sp, - DomCorpus const& domCorpus) - : list_(list) - , sp_(sp) - , domCorpus_(domCorpus) - { - } - - std::size_t size() const noexcept override - { - return list_.size(); - } - - dom::Value get(std::size_t i) const override - { - MRDOCS_ASSERT(i < list_.size()); - return domCorpus_.get(list_[i]->id); - } -}; - class DomTranche : public dom::DefaultObjectImpl { std::shared_ptr tranche_; @@ -635,42 +606,12 @@ class DomTranche : public dom::DefaultObjectImpl } }; -class DomInterface : public dom::LazyObjectImpl -{ - RecordInfo const& I_; - DomCorpus const& domCorpus_; - std::shared_ptr mutable sp_; - -public: - DomInterface( - RecordInfo const& I, - DomCorpus const& domCorpus) - : I_(I) - , domCorpus_(domCorpus) - { - } - - dom::Object - construct() const override - { - sp_ = std::make_shared(makeInterface(I_, *domCorpus_)); - return dom::Object({ - { "public", dom::newObject(sp_->Public, domCorpus_) }, - { "protected", dom::newObject(sp_->Protected, domCorpus_) }, - { "private", dom::newObject(sp_->Private, domCorpus_) }, - // { "overloads", dom::newArray(sp_->Overloads, domCorpus_) }, - // { "static-overloads", dom::newArray(sp_->StaticOverloads, domCorpus_) } - }); - } -}; - //------------------------------------------------ // // Info // //------------------------------------------------ -static std::string_view getDefaultAccess( RecordInfo const& I) noexcept @@ -687,234 +628,6 @@ getDefaultAccess( } } -template -requires std::derived_from -class DomInfo : public dom::LazyObjectImpl -{ - T const& I_; - DomCorpus const& domCorpus_; - -public: - DomInfo( - T const& I, - DomCorpus const& domCorpus) noexcept - : I_(I) - , domCorpus_(domCorpus) - { - } - - dom::Object construct() const override; -}; - -template -requires std::derived_from -dom::Object -DomInfo::construct() const -{ - storage_type entries; - entries.insert(entries.end(), { - { "id", toBase16(I_.id) }, - { "kind", toString(I_.Kind) }, - { "access", toString(I_.Access) }, - { "implicit", I_.Implicit }, - { "namespace", dom::newArray( - I_.Namespace, domCorpus_) }, - { "doc", domCreate(I_.javadoc, domCorpus_) } - }); - if(! I_.Name.empty()) - entries.emplace_back("name", I_.Name); - if(! I_.Namespace.empty()) - entries.emplace_back("parent", - domCorpus_.get(I_.Namespace.front())); - - if constexpr(std::derived_from) - { - entries.insert(entries.end(), { - { "members", dom::newArray(I_.Members, domCorpus_) }, - { "overloads", dom::newArray(I_, domCorpus_)}, - }); - } - if constexpr(std::derived_from) - { - entries.emplace_back("loc", domCreate(I_)); - } - if constexpr(T::isNamespace()) - { - entries.emplace_back("interface", dom::newObject( - std::make_shared( - makeTranche(I_, *domCorpus_)), - domCorpus_)); - entries.emplace_back("usingDirectives", dom::newArray( - I_.UsingDirectives, domCorpus_)); - } - if constexpr(T::isRecord()) - { - entries.insert(entries.end(), { - { "tag", toString(I_.KeyKind) }, - { "defaultAccess", getDefaultAccess(I_) }, - { "isTypedef", I_.IsTypeDef }, - { "bases", dom::newArray(I_.Bases, domCorpus_) }, - { "interface", dom::newObject(I_, domCorpus_) }, - { "template", domCreate(I_.Template, domCorpus_) } - }); - } - if constexpr(T::isEnum()) - { - entries.insert(entries.end(), { - { "type", domCreate(I_.UnderlyingType, domCorpus_) }, - { "isScoped", I_.Scoped } - }); - } - if constexpr(T::isFunction()) - { - auto const set_flag = - [&](dom::String key, bool set) - { - if(set) - entries.emplace_back(std::move(key), true); - }; - set_flag("isVariadic", I_.IsVariadic); - set_flag("isVirtual", I_.IsVirtual); - set_flag("isVirtualAsWritten", I_.IsVirtualAsWritten); - set_flag("isPure", I_.IsPure); - set_flag("isDefaulted", I_.IsDefaulted); - set_flag("isExplicitlyDefaulted",I_.IsExplicitlyDefaulted); - set_flag("isDeleted", I_.IsDeleted); - set_flag("isDeletedAsWritten", I_.IsDeletedAsWritten); - set_flag("isNoReturn", I_.IsNoReturn); - set_flag("hasOverrideAttr", I_.HasOverrideAttr); - set_flag("hasTrailingReturn", I_.HasTrailingReturn); - set_flag("isConst", I_.IsConst); - set_flag("isVolatile", I_.IsVolatile); - set_flag("isFinal", I_.IsFinal); - set_flag("isNodiscard", I_.IsNodiscard); - set_flag("isExplicitObjectMemberFunction", I_.IsExplicitObjectMemberFunction); - - auto const set_string = - [&](dom::String key, dom::String value) - { - if(! value.empty()) - entries.emplace_back(std::move(key), std::move(value)); - }; - set_string("constexprKind", toString(I_.Constexpr)); - set_string("storageClass", toString(I_.StorageClass)); - set_string("refQualifier", toString(I_.RefQualifier)); - - entries.insert(entries.end(), { - { "class", toString(I_.Class) }, - { "params", dom::newArray(I_.Params, domCorpus_) }, - { "return", domCreate(I_.ReturnType, domCorpus_) }, - { "template", domCreate(I_.Template, domCorpus_) }, - { "overloadedOperator", I_.OverloadedOperator }, - }); - - entries.emplace_back("exceptionSpec", toString(I_.Noexcept)); - entries.emplace_back("explicitSpec", toString(I_.Explicit)); - entries.emplace_back("requires", dom::stringOrNull(I_.Requires.Written)); - - #if 0 - if(I_.Noexcept.Kind != NoexceptKind::None) - { - dom::String exceptSpec = "noexcept"; - if(! I_.Noexcept.Operand.empty()) - exceptSpec = fmt::format( - "noexcept({})", - I_.Noexcept.Operand); - entries.emplace_back("exceptionSpec", std::move(exceptSpec)); - } - #endif - } - if constexpr(T::isTypedef()) - { - entries.insert(entries.end(), { - { "type", domCreate(I_.Type, domCorpus_) }, - { "template", domCreate(I_.Template, domCorpus_) }, - { "isUsing", I_.IsUsing } - }); - } - if constexpr(T::isVariable()) - { - entries.insert(entries.end(), { - { "type", domCreate(I_.Type, domCorpus_) }, - { "template", domCreate(I_.Template, domCorpus_) }, - { "constexprKind", toString(I_.Constexpr) }, - { "storageClass", toString(I_.StorageClass) }, - { "isConstinit", I_.IsConstinit }, - { "isThreadLocal", I_.IsThreadLocal }, - { "initializer", dom::stringOrNull(I_.Initializer.Written) }, - }); - } - if constexpr(T::isField()) - { - entries.insert(entries.end(), { - { "type", domCreate(I_.Type, domCorpus_) }, - { "default", dom::stringOrNull(I_.Default.Written) }, - { "isMaybeUnused", I_.IsMaybeUnused }, - { "isDeprecated", I_.IsDeprecated }, - { "isVariant", I_.IsVariant }, - { "isMutable", I_.IsMutable }, - { "isBitfield", I_.IsBitfield }, - { "hasNoUniqueAddress", I_.HasNoUniqueAddress } - }); - if(I_.IsBitfield) - entries.emplace_back("bitfieldWidth", - I_.BitfieldWidth.Written); - } - if constexpr(T::isSpecialization()) - { - } - if constexpr(T::isFriend()) - { - if(I_.FriendSymbol) - { - auto befriended = domCorpus_.get(I_.FriendSymbol); - entries.emplace_back("name", befriended.get("name")); - entries.emplace_back("symbol", befriended); - } - else if(I_.FriendType) - { - auto befriended = domCreate(I_.FriendType, domCorpus_); - entries.emplace_back("name", befriended.get("name")); - entries.emplace_back("type", befriended); - } - } - if constexpr(T::isAlias()) - { - MRDOCS_ASSERT(I_.AliasedSymbol); - entries.emplace_back("aliasedSymbol", domCreate(I_.AliasedSymbol, domCorpus_)); - } - if constexpr(T::isUsing()) - { - entries.emplace_back("class", toString(I_.Class)); - entries.emplace_back("shadows", dom::newArray(I_.UsingSymbols, domCorpus_)); - entries.emplace_back("qualifier", domCreate(I_.Qualifier, domCorpus_)); - } - if constexpr(T::isEnumerator()) - { - entries.insert(entries.end(), { - { "initializer", dom::stringOrNull(I_.Initializer.Written) } - }); - } - if constexpr(T::isGuide()) - { - entries.insert(entries.end(), { - { "params", dom::newArray(I_.Params, domCorpus_) }, - { "deduced", domCreate(I_.Deduced, domCorpus_) }, - { "template", domCreate(I_.Template, domCorpus_) } - }); - - entries.emplace_back("explicitSpec", toString(I_.Explicit)); - } - if constexpr(T::isConcept()) - { - entries.insert(entries.end(), { - { "template", domCreate(I_.Template, domCorpus_) }, - { "constraint", dom::stringOrNull(I_.Constraint.Written) } - }); - } - return dom::Object(std::move(entries)); -} - //------------------------------------------------ } // (anon) @@ -1008,6 +721,269 @@ operator->() const return &getCorpus(); } +namespace dom { + /* Determine if a type has a mrdocs::toString overload + */ + template + concept HasMrDocsToString = requires(U u) + { + { mrdocs::toString(u) } -> std::convertible_to; + }; + + /* Convert enum Values to strings using mrdocs::toString + */ + template + requires std::is_enum_v + struct ToValue + { + std::string_view + operator()(U const& v) const + { + return mrdocs::toString(v); + } + }; + + static_assert(HasToValue); + static_assert(HasToValue); + + /* Convert SymbolID to strings using toBase16 + */ + template <> + struct ToValue + { + std::string + operator()(SymbolID const& id) const + { + return toBase16(id); + } + }; + + static_assert(HasToValue); + + /* Mapping Traits for an Info type + + These traits map an Info type to a DOM object. + It includes all members of the derived type. + + The traits store a reference to the DomCorpus + so that it can resolve symbol IDs to the corresponding + Info objects. Whenever a member refers to symbol IDs, + the mapping trait will automatically resolve the + symbol ID to the corresponding Info object. + + This allows all references to be resolved to the + corresponding Info object lazily from the templates + that use the DOM. + */ + template T> + struct MappingTraits + { + private: + DomCorpus const* domCorpus_ = nullptr; + + public: + MappingTraits(DomCorpus const& domCorpus) + : domCorpus_(&domCorpus) + { + } + + template + void + map(IO &io, T const& I) const + { + MRDOCS_ASSERT(domCorpus_); + io.map("id", I.id); + if (!I.Name.empty()) + { + io.map("name", I.Name); + } + io.map("kind", I.Kind); + io.map("access", I.Access); + io.map("implicit", I.Implicit); + io.defer("namespace", [&]{ return dom::newArray(I.Namespace, *domCorpus_); }); + io.defer("doc", [&]{ return domCreate(I.javadoc, *domCorpus_); }); + if (!I.Namespace.empty()) + { + io.defer("parent", [&]{ return domCorpus_->get(I.Namespace.front()); }); + } + if constexpr(std::derived_from) + { + io.defer("members", [&]{ return dom::newArray(I.Members, *domCorpus_); }); + io.defer("overloads", [&]{ return dom::newArray(I, *domCorpus_); }); + } + if constexpr(std::derived_from) + { + io.defer("loc", [&]{ return domCreate(I); }); + } + if constexpr(T::isNamespace()) + { + io.defer("interface", [&]{ return dom::newObject( + std::make_shared( + makeTranche(I, **domCorpus_)), + *domCorpus_); }); + io.defer("usingDirectives", [&]{ return dom::newArray( + I.UsingDirectives, *domCorpus_); }); + } + if constexpr (T::isRecord()) + { + io.map("tag", I.KeyKind); + io.defer("defaultAccess", [&]{ return getDefaultAccess(I); }); + io.map("isTypedef", I.IsTypeDef); + io.defer("bases", [&]{ return dom::newArray(I.Bases, *domCorpus_); }); + io.defer("interface", [&]{ + auto sp = std::make_shared(makeInterface(I, domCorpus_->getCorpus())); + return dom::Object({ + { "public", dom::newObject(sp->Public, *domCorpus_) }, + { "protected", dom::newObject(sp->Protected, *domCorpus_) }, + { "private", dom::newObject(sp->Private, *domCorpus_) }, + // { "overloads", dom::newArray(sp->Overloads, *domCorpus_) }, + // { "static-overloads", dom::newArray(sp->StaticOverloads, *domCorpus_) } + }); + }); + io.defer("template", [&]{ return domCreate(I.Template, *domCorpus_); }); + } + if constexpr (T::isEnum()) + { + io.defer("type", [&]{ return domCreate(I.UnderlyingType, *domCorpus_); }); + io.map("isScoped", I.Scoped); + } + if constexpr (T::isFunction()) + { + io.map("isVariadic", I.IsVariadic); + io.map("isVirtual", I.IsVirtual); + io.map("isVirtualAsWritten", I.IsVirtualAsWritten); + io.map("isPure", I.IsPure); + io.map("isDefaulted", I.IsDefaulted); + io.map("isExplicitlyDefaulted", I.IsExplicitlyDefaulted); + io.map("isDeleted", I.IsDeleted); + io.map("isDeletedAsWritten", I.IsDeletedAsWritten); + io.map("isNoReturn", I.IsNoReturn); + io.map("hasOverrideAttr", I.HasOverrideAttr); + io.map("hasTrailingReturn", I.HasTrailingReturn); + io.map("isConst", I.IsConst); + io.map("isVolatile", I.IsVolatile); + io.map("isFinal", I.IsFinal); + io.map("isNodiscard", I.IsNodiscard); + io.map("isExplicitObjectMemberFunction", I.IsExplicitObjectMemberFunction); + if (I.Constexpr != ConstexprKind::None) + { + io.map("constexprKind", I.Constexpr); + } + if (I.StorageClass != StorageClassKind::None) + { + io.map("storageClass", I.StorageClass); + } + if (I.RefQualifier != ReferenceKind::None) + { + io.map("refQualifier", I.RefQualifier); + } + io.map("class", I.Class); + io.defer("params", [&]{ return dom::newArray(I.Params, *domCorpus_); }); + io.defer("return", [&]{ return domCreate(I.ReturnType, *domCorpus_); }); + io.defer("template", [&]{ return domCreate(I.Template, *domCorpus_); }); + io.map("overloadedOperator", I.OverloadedOperator); + io.defer("exceptionSpec", [&]{ return toString(I.Noexcept); }); + io.defer("explicitSpec", [&]{ return toString(I.Explicit); }); + if (!I.Requires.Written.empty()) + { + io.map("requires", I.Requires.Written); + } + } + if constexpr (T::isTypedef()) + { + io.defer("type", [&]{ return domCreate(I.Type, *domCorpus_); }); + io.defer("template", [&]{ return domCreate(I.Template, *domCorpus_); }); + io.map("isUsing", I.IsUsing); + } + if constexpr (T::isVariable()) + { + io.defer("type", [&]{ return domCreate(I.Type, *domCorpus_); }); + io.defer("template", [&]{ return domCreate(I.Template, *domCorpus_); }); + if (I.Constexpr != ConstexprKind::None) + { + io.map("constexprKind", I.Constexpr); + } + if (I.StorageClass != StorageClassKind::None) + { + io.map("storageClass", I.StorageClass); + } + io.map("isConstinit", I.IsConstinit); + io.map("isThreadLocal", I.IsThreadLocal); + if (!I.Initializer.Written.empty()) + { + io.map("initializer", I.Initializer.Written); + } + } + if constexpr (T::isField()) + { + io.defer("type", [&]{ return domCreate(I.Type, *domCorpus_); }); + if (!I.Default.Written.empty()) + { + io.map("default", I.Default.Written); + } + io.map("isMaybeUnused", I.IsMaybeUnused); + io.map("isDeprecated", I.IsDeprecated); + io.map("isVariant", I.IsVariant); + io.map("isMutable", I.IsMutable); + io.map("isBitfield", I.IsBitfield); + io.map("hasNoUniqueAddress", I.HasNoUniqueAddress); + if (I.IsBitfield) + { + io.map("bitfieldWidth", I.BitfieldWidth.Written); + } + } + if constexpr (T::isSpecialization()) + {} + if constexpr (T::isFriend()) + { + if (I.FriendSymbol) + { + io.defer("name", [&]{ return domCorpus_->get(I.FriendSymbol).get("name"); }); + io.defer("symbol", [&]{ return domCorpus_->get(I.FriendSymbol); }); + } + else if (I.FriendType) + { + io.defer("name", [&]{ return domCreate(I.FriendType, *domCorpus_).get("name"); }); + io.defer("type", [&]{ return domCreate(I.FriendType, *domCorpus_); }); + } + } + if constexpr (T::isAlias()) + { + MRDOCS_ASSERT(I.AliasedSymbol); + io.defer("aliasedSymbol", [&]{ return domCreate(I.AliasedSymbol, *domCorpus_); }); + } + if constexpr (T::isUsing()) + { + io.map("class", I.Class); + io.defer("shadows", [&]{ return dom::newArray(I.UsingSymbols, *domCorpus_); }); + io.defer("qualifier", [&]{ return domCreate(I.Qualifier, *domCorpus_); }); + } + if constexpr (T::isEnumerator()) + { + if (!I.Initializer.Written.empty()) + { + io.map("initializer", I.Initializer.Written); + } + } + if constexpr (T::isGuide()) + { + io.defer("params", [&]{ return dom::newArray(I.Params, *domCorpus_); }); + io.defer("deduced", [&]{ return domCreate(I.Deduced, *domCorpus_); }); + io.defer("template", [&]{ return domCreate(I.Template, *domCorpus_); }); + io.defer("explicitSpec", [&]{ return toString(I.Explicit); }); + } + if constexpr (T::isConcept()) + { + io.defer("template", [&]{ return domCreate(I.Template, *domCorpus_); }); + if (!I.Constraint.Written.empty()) + { + io.map("constraint", I.Constraint.Written); + } + } + } + }; +} + dom::Object DomCorpus:: construct(Info const& I) const @@ -1015,7 +991,7 @@ construct(Info const& I) const return visit(I, [&](T const& I) { - return dom::newObject>(I, *this); + return dom::newObject>(I, dom::MappingTraits(*this)); }); } diff --git a/src/test/lib/Dom/LazyObject.cpp b/src/test/lib/Dom/LazyObject.cpp new file mode 100644 index 000000000..72b7e28f7 --- /dev/null +++ b/src/test/lib/Dom/LazyObject.cpp @@ -0,0 +1,255 @@ +// +// Licensed 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 +// +// Copyright (c) 2024 Alan de Freitas (alandefreitas@gmail.com) +// +// Official repository: https://github.com/cppalliance/mrdocs +// + +#include "lib/Dom/LazyObject.hpp" +#include + +namespace clang { +namespace mrdocs { +namespace dom { + +struct Y { + std::string a = "hello"; + std::string b = "world"; +}; + +template <> +struct ToValue { + Value + operator()(Y const& y) const + { + return y.a + " " + y.b; + } +}; + +struct X { + int i = 123; + std::string s = "hello"; + Y y; +}; + +template<> +struct MappingTraits +{ + template + void map(IO &io, X const& x) const + { + io.map("i", x.i); + io.map("s", x.s); + io.defer("si", [&x]{ return x.s + std::to_string(x.i); }); + io.map("y", x.y); + } +}; + +struct LazyObject_test +{ + void + testConstructor() + { + X x; + LazyObjectImpl obj(x); + } + + void + testTypeKey() + { + X x; + LazyObjectImpl obj(x); + BOOST_TEST(std::string_view(obj.type_key()) == "LazyObject"); + } + + void + testGet() + { + X x; + LazyObjectImpl obj(x); + + // Convertible to Value + { + BOOST_TEST(obj.get("i") == 123); + BOOST_TEST(obj.get("s") == "hello"); + } + + // Change value through the original object + { + x.i = 789; + BOOST_TEST(obj.get("i") == 789); + } + } + + void + testSet() + { + X x; + LazyObjectImpl obj(x); + + // Change value + { + obj.set("i", 456); + BOOST_TEST(obj.get("i") == 456); + + // Changing value via the original object + // no longer affects the LazyObject + x.i = 789; + BOOST_TEST(obj.get("i") == 456); + } + + // Make undefined + { + obj.set("i", Value{}); + BOOST_TEST(obj.get("i").isUndefined()); + } + + // Add new value + { + obj.set("x", 789); + BOOST_TEST(obj.get("x") == 789); + } + } + + void + testExists() + { + X x; + LazyObjectImpl obj(x); + + // original members + { + BOOST_TEST(obj.exists("i")); + BOOST_TEST(obj.exists("s")); + BOOST_TEST_NOT(obj.exists("x")); + } + + // new members + { + obj.set("x", 789); + BOOST_TEST(obj.exists("x")); + } + } + + void + testSize() + { + X x; + LazyObjectImpl obj(x); + + // original object + { + BOOST_TEST(obj.size() == 2); + } + + // new values + { + obj.set("x", 789); + BOOST_TEST(obj.size() == 3); + } + + // replacing in overlay doesn't increase size + { + obj.set("i", 456); + BOOST_TEST(obj.size() == 3); + } + + // undefined values don't reduce the size + { + obj.set("i", Value{}); + BOOST_TEST(obj.size() == 3); + } + } + + void + testVisit() + { + X x; + LazyObjectImpl obj(x); + + // visit original members + { + std::size_t count = 0; + bool match = true; + obj.visit([&count, &match](String key, Value value) { + if (key == "i" && value != 123) + match = false; + if (key == "s" && value != "hello") + match = false; + if (key == "si" && value != "hello123") + match = false; + if (key == "y" && value != "hello world") + match = false; + ++count; + return true; + }); + BOOST_TEST(count == 4); + } + + // visit new members + { + obj.set("x", 789); + std::size_t count = 0; + bool found = false; + bool match = true; + obj.visit([&count, &match, &found](String key, Value value) { + if (key == "x" && value == 789) + found = true; + ++count; + return true; + }); + BOOST_TEST(count == 5); + BOOST_TEST(found); + } + + // stop visiting + { + std::size_t count = 0; + obj.visit([&count](String key, Value value) { + ++count; + return false; + }); + BOOST_TEST(count == 1); + } + + // replacing in overlay doesn't increase size + { + obj.set("i", 456); + std::size_t count = 0; + bool match = true; + obj.visit([&count, &match](String key, Value value) { + if (key == "i" && value != 456) + match = false; + if (key == "s" && value != "hello") + match = false; + if (key == "x" && value != 789) + match = false; + ++count; + return true; + }); + BOOST_TEST(count == 5); + } + } + + void run() + { + testConstructor(); + testTypeKey(); + testGet(); + testSet(); + testExists(); + testVisit(); + } +}; + +TEST_SUITE( + LazyObject_test, + "clang.mrdocs.dom.LazyObject"); + +} // dom +} // mrdocs +} // clang +