diff --git a/include/Basic/Document.h b/include/Basic/Document.h index 9cc72a4e..fdd62798 100644 --- a/include/Basic/Document.h +++ b/include/Basic/Document.h @@ -1,7 +1,6 @@ #pragma once #include "Location.h" -#include "Support/Reflection.h" namespace clice::proto { @@ -25,7 +24,9 @@ struct TextDocumentIdentifier { DocumentUri uri; }; -CLICE_RECORD(VersionedTextDocumentIdentifier, TextDocumentIdentifier) { +struct VersionedTextDocumentIdentifier { + /// The text document's URI. + DocumentUri uri; /// The version number of this document. /// /// The version number of a document will increase after each change, diff --git a/include/Compiler/Semantic.h b/include/Compiler/Semantic.h index 6af3265f..737fa5ee 100644 --- a/include/Compiler/Semantic.h +++ b/include/Compiler/Semantic.h @@ -7,7 +7,7 @@ namespace clice { -enum class RelationKinds : uint32_t { +enum class RelationKind : uint32_t { Invalid, Declaration, Definition, @@ -36,11 +36,11 @@ enum class RelationKinds : uint32_t { Callee, }; -struct RelationKind : enum_type { - using enum RelationKinds; - using enum_type::enum_type; - using enum_type::operator=; -}; +// struct RelationKind : enum_type { +// using enum RelationKinds; +// using enum_type::enum_type; +// using enum_type::operator=; +// }; enum class OccurrenceKind { /// This occurrence directly corresponds to a unique source symbol. @@ -53,6 +53,10 @@ enum class OccurrenceKind { Instantiation, }; +enum class SymbolKind { + +}; + template class SemanticVisitor : public clang::RecursiveASTVisitor> { public: diff --git a/include/Compiler/SymbolKinds.h b/include/Compiler/SymbolKinds.h new file mode 100644 index 00000000..1d978863 --- /dev/null +++ b/include/Compiler/SymbolKinds.h @@ -0,0 +1,62 @@ +#include "Utility.h" +#include "Support/Enum.h" + +namespace clice { + +namespace impl { + +/// In the LSP, there are several different kinds, such as `SemanticTokenType`, +/// `CompletionItemKind`, and `SymbolKind`. Unfortunately, these kinds do not cover all the semantic +/// information we need. It's also inconsistent that some kinds exist in one category but not in +/// another, for example, `Namespace` is present in `SemanticTokenType` but not in +/// `CompletionItemKind`. To address this, we define our own `SymbolKind`, which will be used +/// consistently across our responses to the client and in the index. Users who prefer to stick to +/// standard LSP kinds can map our `SymbolKind` to the corresponding LSP kinds through +/// configuration. +enum class SymbolKind { + Invalid = 0, + Comment, ///< C/C++ comments. + Number, ///< C/C++ number literal. + Character, ///< C/C++ character literal. + String, ///< C/C++ string literal. + Keyword, ///< C/C++ keyword. + Directive, ///< C/C++ preprocessor directive, e.g. `#include`. + Header, ///< C/C++ header name, e.g. `` and `"foo.h"`. + Module, ///< C++20 module name. + Macro, ///< C/C++ macro. + MacroParameter, ///< C/C++ macro parameter. + Namespace, ///> C++ namespace. + Class, ///> C/C++ class. + Struct, ///> C/C++ struct. + Union, ///> C/C++ union. + Enum, ///> C/C++ enum. + Type, ///> C/C++ type alias and C++ template type parameter. + Field, ///> C/C++ field. + EnumMember, ///> C/C++ enum member. + Function, ///> C/C++ function. + Method, ///> C++ method. + Variable, ///> C/C++ variable, includes C++17 structured bindings. + Parameter, ///> C/C++ parameter. + Label, ///> C/C++ label. + Concept, ///> C++20 concept. + Attribute, ///> GNU/MSVC/C++11/C23 attribute. + Operator, ///> C/C++ operator. + Paren, ///> `(` and `)`. + Bracket, ///> `[` and `]`. + Brace, ///> `{` and `}`. + Angle, ///> `<` and `>`. +}; + +} // namespace impl + +struct SymbolKind { + static SymbolKind from(const clang::tok::TokenKind kind); + + static SymbolKind from(const clang::Decl::Kind kind); +}; + +enum class SymbolModifierKind { + +}; + +} // namespace clice diff --git a/include/Feature/CodeCompletion.h b/include/Feature/CodeCompletion.h index 08aea680..64d6f56f 100644 --- a/include/Feature/CodeCompletion.h +++ b/include/Feature/CodeCompletion.h @@ -16,6 +16,7 @@ struct CompletionOptions { }; enum class CompletionItemKind { + Invalid = 0, Text = 1, Method = 2, Function = 3, diff --git a/include/Index/Loader.h b/include/Index/Loader.h index 959428f5..e9a830a0 100644 --- a/include/Index/Loader.h +++ b/include/Index/Loader.h @@ -126,11 +126,11 @@ public: template bool lookupRelation(const binary::Symbol* symbol, - RelationKinds kind, + RelationKind::Kind kind, const Callback& callback) { if(symbol) { for(auto& relation: relations(*symbol)) { - if(relation.kind.is(kind)) { + if(relation.kind & kind) { FullLocation location = decompose(relation.location); callback(location); } diff --git a/include/Index/Relation.h b/include/Index/Relation.h index 8abc42be..ba0cca43 100644 --- a/include/Index/Relation.h +++ b/include/Index/Relation.h @@ -2,40 +2,38 @@ namespace clice::index { -enum class RelationKinds : uint32_t { - Invalid, - Declaration, - Definition, - Reference, - // Write Relation. - Read, - Write, - Interface, - Implementation, - /// When target is a type definition of source, source is possible type or constructor. - TypeDefinition, - - /// When target is a base class of source. - Base, - /// When target is a derived class of source. - Derived, - - /// When target is a constructor of source. - Constructor, - /// When target is a destructor of source. - Destructor, - - // When target is a caller of source. - Caller, - // When target is a callee of source. - Callee, -}; - /// A bit field enum to describe the kind of relation between two symbols. -struct RelationKind : enum_type { - using enum RelationKinds; - using enum_type::enum_type; - using enum_type::operator=; +struct RelationKind : support::Enum { + using Enum::Enum; + + enum Kind : uint32_t { + Invalid, + Declaration, + Definition, + Reference, + // Write Relation. + Read, + Write, + Interface, + Implementation, + /// When target is a type definition of source, source is possible type or constructor. + TypeDefinition, + + /// When target is a base class of source. + Base, + /// When target is a derived class of source. + Derived, + + /// When target is a constructor of source. + Constructor, + /// When target is a destructor of source. + Destructor, + + // When target is a caller of source. + Caller, + // When target is a callee of source. + Callee, + }; }; template @@ -76,11 +74,11 @@ struct Relation { static bool isSymbol(const Relation& relation) { auto kind = relation.kind; - return kind.is(RelationKinds::Interface) || kind.is(RelationKinds::Implementation) || - kind.is(RelationKinds::TypeDefinition) || kind.is(RelationKinds::Base) || - kind.is(RelationKinds::Derived) || kind.is(RelationKinds::Constructor) || - kind.is(RelationKinds::Destructor) || kind.is(RelationKinds::Caller) || - kind.is(RelationKinds::Callee); + return kind & RelationKind::Interface || kind & RelationKind::Implementation || + kind & RelationKind::TypeDefinition || kind & RelationKind::Base || + kind & RelationKind::Derived || kind & RelationKind::Constructor || + kind & RelationKind::Destructor || kind & RelationKind::Caller || + kind & RelationKind::Callee; } }; diff --git a/include/Server/Server.h b/include/Server/Server.h index 2a73c9f5..eb973739 100644 --- a/include/Server/Server.h +++ b/include/Server/Server.h @@ -7,8 +7,7 @@ #include "Basic/URI.h" #include "Basic/Document.h" #include "Compiler/Compiler.h" -#include "Support/JSON.h" -#include "Support/FileSystem.h" +#include "Support/Support.h" #include "Feature/Lookup.h" #include "Feature/DocumentHighlight.h" diff --git a/include/Support/ADT.h b/include/Support/ADT.h index 9f57b4e3..47f0d3f2 100644 --- a/include/Support/ADT.h +++ b/include/Support/ADT.h @@ -9,7 +9,7 @@ #include #include #include - +#include #include namespace clice {} // namespace clice diff --git a/include/Support/Compare.h b/include/Support/Compare.h new file mode 100644 index 00000000..d613db8e --- /dev/null +++ b/include/Support/Compare.h @@ -0,0 +1,42 @@ +#pragma once + +#include "Struct.h" + +namespace clice::support { + +constexpr auto equal(const auto& lhs, const auto& rhs) { + if constexpr(requires { lhs == rhs; }) { + return (lhs == rhs); + } else { + return support::foreach(lhs, rhs, [](const auto& lhs, const auto& rhs) { + return support::equal(lhs, rhs); + }); + } +} + +/// Compare lhs and rhs according to the dictionary order of their members. +constexpr auto less(const auto& lhs, const auto& rhs) { + if constexpr(requires { lhs < rhs; }) { + return (lhs < rhs); + } else { + bool result = false; + support::foreach(lhs, rhs, [&result](const auto& lhs, const auto& rhs) { + /// if lhs less than rhs, abort the iteration and return true. + if(support::less(lhs, rhs)) { + result = true; + return false; + } + + /// if lhs equal to rhs, continue to next member. + if(support::equal(lhs, rhs)) { + return true; + } + + /// if lhs greater than rhs, abort the iteration and return false. + return false; + }); + return result; + } +} + +} // namespace clice::support diff --git a/include/Support/Enum.h b/include/Support/Enum.h index fd0e5822..f0f240b8 100644 --- a/include/Support/Enum.h +++ b/include/Support/Enum.h @@ -1,97 +1,280 @@ #pragma once -#include -#include +#include +#include +#include +#include +#include +#include -#include +#include "TypeTraits.h" +#include "JSON.h" +#include "Format.h" -namespace clice { +namespace clice::support { template requires std::is_enum_v -auto underlying_value(T value) { +constexpr auto underlying_value(T value) { return static_cast>(value); } -template - requires std::is_enum_v -struct enum_type { - using enum_tag = Enum; +template + requires std::is_enum_v +consteval auto enum_name() { + std::string_view name = std::source_location::current().function_name(); +#if __GNUC__ || __clang__ + std::size_t start = name.find('=') + 2; + std::size_t end = name.size() - 1; +#elif _MSC_VER + std::size_t start = name.find('<') + 1; + std::size_t end = name.rfind(">("); +#else + static_assert(false, "Not supported compiler"); +#endif + name = name.substr(start, end - start); + start = name.rfind("::"); + return start == std::string_view::npos ? name : name.substr(start + 2); +} - constexpr explicit enum_type() = default; +template +consteval auto enum_max() { + constexpr auto value = std::bit_cast(static_cast>(N)); + if constexpr(enum_name().find(")") == std::string_view::npos) + return enum_max(); + else + return N; +} - constexpr explicit enum_type(std::underlying_type_t value) : value(value) {} - - constexpr enum_type(Enum enum_) : value(underlying_value(enum_)) {} - - friend constexpr bool operator== (enum_type lhs, enum_type rhs) = default; - - friend llvm::raw_ostream& operator<< (llvm::raw_ostream& os, enum_type e) { - os << std::format("[normal enum, value: {}, name: {}]", - e.value, - refl::enum_name(static_cast(e.value))); - return os; - } - - std::underlying_type_t value; +template +struct enum_table { + constexpr static std::array table = + [](std::index_sequence) { + return std::array{enum_name(Is)>()...}; + }(std::make_index_sequence{}); }; -template - requires std::is_enum_v -struct enum_type { +template ()> +constexpr std::string_view enum_name(E value) { + return enum_table::table[static_cast>(value) - begin]; +} + +/// A helper class to define enum. +template +class Enum { public: - using enum_tag = Enum; + /// A integral must explicitly convert to the enum. + explicit constexpr Enum(underlying value) : m_Value(value) {} - constexpr explicit enum_type() = default; - - template - constexpr explicit enum_type(Args... args) : value(((1 << underlying_value(args)) | ...)) {} - - constexpr explicit enum_type(std::underlying_type_t value) : value(value) {} - - constexpr enum_type(Enum enum_) : value(1 << underlying_value(enum_)) {} - - constexpr void set(Enum enum_) { - value |= (1 << underlying_value(enum_)); + /// Allow the enum to be constructed from the enum value. + template + requires std::is_same_v + constexpr Enum(Kind kind) : m_Value(kind) { + static_assert(sizeof(underlying) >= sizeof(typename Derived::Kind), + "Underlying type is too small to hold all enum values."); } - constexpr bool is(Enum enum_) const { - return value & (1 << underlying_value(enum_)); + constexpr Enum(const Enum&) = default; + + constexpr Enum& operator= (const Enum&) = default; + + /// Get the underlying value of the enum. + constexpr underlying value() const { + return m_Value; } - friend constexpr bool operator== (enum_type lhs, enum_type rhs) = default; + /// Get the name of the enum. + constexpr std::string_view name() const { + using E = typename Derived::Kind; + return support::enum_name(static_cast(m_Value)); + } - std::string dump() { + constexpr explicit operator bool () { + return m_Value != invalid(); + } + + constexpr friend bool operator== (Enum lhs, Enum rhs) = default; + + constexpr inline static bool is_enum = true; + + constexpr static auto& all() { + return enum_table::table; + } + +private: + consteval static underlying begin() { + if constexpr(requires { Derived::FirstEnum; }) { + return Derived::FirstEnum; + } else { + return 0; + } + } + + consteval static underlying end() { + if constexpr(requires { Derived::LastEnum; }) { + return Derived::LastEnum; + } else { + return support::enum_max(); + } + } + + consteval static underlying invalid() { + if constexpr(requires { Derived::InvalidEnum; }) { + return Derived::InvalidEnum; + } else { + static_assert(dependent_false, "Invalid enum value is not defined."); + } + } + +private: + underlying m_Value; +}; + +template +class Enum { +public: + /// FIXME: + Enum() = default; + + /// A integral must explicitly convert to the enum. + explicit constexpr Enum(underlying value) : m_Value(value) {} + + /// Allow the enum to be constructed from the enum value. + template + requires (std::is_same_v && ...) + constexpr Enum(Kinds... kind) : m_Value(((1 << underlying_value(kind)) | ...)) { + static_assert(sizeof(underlying) * 8 >= end(), + "Underlying type is too small to hold all enum values."); + } + + constexpr Enum(const Enum&) = default; + + constexpr Enum& operator= (const Enum&) = default; + + /// Get the underlying value of the enum. + constexpr underlying value() const { + return m_Value; + } + + /// Get the name of the enum. + constexpr std::string name() const { std::string masks; - bool is_first = true; - for(std::size_t i = 0; i < sizeof(std::underlying_type_t) * 8; i++) { - if(value & (1 << i)) { - if(is_first) { - is_first = false; - } else { - masks += " | "; - } - masks += refl::enum_name(static_cast(i)); + bool isFirst = true; + for(std::size_t i = 0; i < sizeof(underlying) * 8; i++) { + bool hasBit = m_Value & (1 << i); + + if(!hasBit) { + continue; } + + if(isFirst) { + isFirst = false; + } else { + masks += " | "; + } + + using E = typename Derived::Kind; + masks += support::enum_name(static_cast(i)); } return masks; } - friend llvm::raw_ostream& operator<< (llvm::raw_ostream& os, enum_type e) { - os << std::format("[mask enum, value: {}, masks: {}]", e.value, e.dump()); - return os; + constexpr inline static bool is_enum = true; + + constexpr static auto& all() { + return enum_table::table; } - std::underlying_type_t value; + constexpr explicit operator bool () { + return m_Value != 0; + } + + constexpr friend bool operator== (Enum lhs, Enum rhs) = default; + + template + requires std::is_same_v + constexpr Enum operator| (Kind kind) const { + return Enum(m_Value | (1 << underlying_value(kind))); + } + + template + requires std::is_same_v + constexpr Enum operator& (Kind kind) const { + return Enum(m_Value & (1 << underlying_value(kind))); + } + + template + requires std::is_same_v + constexpr Enum& operator|= (Kind kind) { + m_Value |= (1 << underlying_value(kind)); + return *this; + } + + template + requires std::is_same_v + constexpr Enum& operator&= (Kind kind) { + m_Value &= (1 << underlying_value(kind)); + return *this; + } + +private: + consteval static std::size_t begin() { + if constexpr(requires { Derived::FirstEnum; }) { + return Derived::FirstEnum; + } else { + return 0; + } + } + + consteval static std::size_t end() { + if constexpr(requires { Derived::LastEnum; }) { + return Derived::LastEnum; + } else { + return support::enum_max(); + } + } + +private: + underlying m_Value; }; template - requires requires { typename T::enum_tag; } -auto underlying_value(T value) { - return value.value; -} +constexpr bool is_enum_v = requires { + T::is_enum; + requires T::is_enum; +}; -template -constexpr inline bool is_enum_v = std::is_enum_v || requires { typename T::enum_tag; }; +} // namespace clice::support -} // namespace clice +namespace clice::json { + +template + requires support::is_enum_v +struct Serde { + static json::Value serialize(const E& e) { + return json::Value(e.value()); + } + + static E deserialize(const json::Value& value) { + assert(value.kind() == json::Value::Number && "Expect a number"); + return E(value.getAsNumber().value()); + } +}; + +} // namespace clice::json + +template + requires clice::support::is_enum_v +struct std::formatter : std::formatter { + using Base = std::formatter; + + template + constexpr auto parse(ParseContext& ctx) { + return Base::parse(ctx); + } + + template + auto format(const E& e, FormatContext& ctx) const { + return Base::format(e.name(), ctx); + } +}; diff --git a/include/Support/JSON.h b/include/Support/JSON.h index 1a17f76e..3826babe 100644 --- a/include/Support/JSON.h +++ b/include/Support/JSON.h @@ -1,95 +1,268 @@ #pragma once #include -#include -#include +#include +#include + +#include "llvm/ADT/ArrayRef.h" +#include "llvm/ADT/StringRef.h" +#include "llvm/Support/JSON.h" +#include "Support/TypeTraits.h" namespace clice::json { using namespace llvm::json; -template -constexpr inline bool is_array_v = false; +/// Specialize this struct to provide custom serialization and deserialization for a type. +template +struct Serde; + +/// Check if the serde if given type is stateful. +template +concept stateful_serde = requires { + Serde::state; + requires Serde::state; +}; + +/// Serialize an object to a JSON value. +template +json::Value serialize(const V& v, Serdes&&... serdes) { + if constexpr(!stateful_serde) { + return Serde::serialize(v); + } else if constexpr(sizeof...(Serdes) > 0) { + using S = Serde; + if constexpr((std::is_same_v> || ...)) { + auto try_each = + [&](auto& self, First&& first, Rest&&... rest) { + if constexpr(std::is_same_v, S>) { + /// If we already have a direct serde, use it. + return std::forward(first).serialize(v); + } else if constexpr(sizeof...(rest) > 0) { + /// Try the next serde. + return self(self, std::forward(rest)...); + } + }; + return try_each(try_each, std::forward(serdes)...); + } else { + return Serde::serialize(v, std::forward(serdes)...); + } + } else { + static_assert(dependent_false, "Stateful serde requires at least one serde"); + } +} + +/// Deserialize a JSON value to an object. +template +T deserialize(const json::Value& value, Serdes&&... serdes) { + if constexpr(!stateful_serde) { + return Serde::deserialize(value); + } else if constexpr(sizeof...(Serdes) > 0) { + using S = Serde; + if constexpr((std::is_same_v> || ...)) { + auto try_each = + [&](auto& self, First&& first, Rest&&... rest) { + if constexpr(std::is_same_v, S>) { + /// If we already have a direct serde, use it. + return std::forward(first).deserialize(value); + } else if constexpr(sizeof...(rest) > 0) { + /// Try the next serde. + return self(self, std::forward(rest)...); + } + }; + return try_each(try_each, std::forward(serdes)...); + } else { + /// Otherwise, pass the serdes to the next serde. + return Serde::deserialize(value, std::forward(serdes)...); + } + + } else { + static_assert(dependent_false, "Stateful Serde requires at least one serde"); + } +} + +template <> +struct Serde { + static json::Value serialize(std::nullptr_t) { + return json::Value(nullptr); + } + + static std::nullptr_t deserialize(const json::Value& value) { + assert(value.kind() == json::Value::Null && "Expect null"); + return nullptr; + } +}; + +template <> +struct Serde { + using V = std::string; + + static json::Value serialize(const V& v) { + return json::Value(v); + } + + static V deserialize(const json::Value& value) { + assert(value.kind() == json::Value::String && "Expect a string"); + return value.getAsString().value().str(); + } +}; + +template <> +struct Serde { + static json::Value serialize(bool v) { + return json::Value(v); + } + + static bool deserialize(const json::Value& value) { + assert(value.kind() == json::Value::Boolean && "Expect boolean"); + return value.getAsBoolean().value(); + } +}; + +template +struct Serde { + static json::Value serialize(I v) { + return json::Value(static_cast(v)); + } + + static I deserialize(const json::Value& value) { + assert(value.kind() == json::Value::Number && "Expect number"); + return static_cast(value.getAsInteger().value()); + } +}; + +template + requires std::is_enum_v +struct Serde { + static json::Value serialize(E v) { + return json::Value(static_cast(v)); + } + + static E deserialize(const json::Value& value) { + assert(value.kind() == json::Value::Number && "Expect number"); + return static_cast(value.getAsInteger().value()); + } +}; + +template +struct Serde { + static json::Value serialize(F v) { + return json::Value(static_cast(v)); + } + + static F deserialize(const json::Value& value) { + assert(value.kind() == json::Value::Number && "Expect number"); + return static_cast(value.getAsNumber().value()); + } +}; + +template <> +struct Serde { + static json::Value serialize(float v) { + return json::Value(v); + } + + static float deserialize(const json::Value& value) { + assert(value.kind() == json::Value::Number && "Expect number"); + return value.getAsNumber().value(); + } +}; + +template <> +struct Serde { + using V = llvm::StringRef; + + static json::Value serialize(const V& v) { + return json::Value(v.str()); + } + + static V deserialize(const json::Value& value) { + assert(value.kind() == json::Value::String && "Expect string"); + return value.getAsString().value(); + } +}; + +template <> +struct Serde { + using V = std::string_view; + + static json::Value serialize(const V& v) { + return json::Value(llvm::StringRef(v.data(), v.size())); + } + + static V deserialize(const json::Value& value) { + assert(value.kind() == json::Value::String && "Expect string"); + return value.getAsString().value(); + } +}; template -constexpr inline bool is_array_v> = true; +struct Serde> { + using V = std::array; -template -constexpr inline bool is_array_v> = true; + constexpr inline static bool state = stateful_serde; -template -constexpr inline bool is_array_v> = true; - -template -constexpr inline bool is_string_v = false; - -template <> -constexpr inline bool is_string_v = true; - -template <> -constexpr inline bool is_string_v = true; - -template <> -constexpr inline bool is_string_v = true; - -template -constexpr inline bool is_integral_v = - std::is_same_v || std::is_same_v || std::is_same_v || - std::is_same_v || std::is_same_v || - std::is_same_v; - -template -json::Value serialize(const Value& value) { - if constexpr(std::is_same_v) { - return value; - } else if constexpr(is_integral_v) { - return static_cast(value); - } else if constexpr(is_enum_v) { - return static_cast(underlying_value(value)); - } else if constexpr(std::is_floating_point_v) { - return static_cast(value); - } else if constexpr(is_string_v) { - return llvm::StringRef(value); - } else if constexpr(is_array_v) { + template + static json::Value serialize(const V& v, Serdes&&... serdes) { json::Array array; - for(const auto& element: value) { - array.push_back(serialize(element)); + for(const auto& element: v) { + array.push_back(json::serialize(element, std::forward(serdes)...)); } return array; - } else { - json::Object object; - refl::foreach(value, [&](llvm::StringRef name, const auto& field) { - object.try_emplace(name, serialize(field)); - }); - return object; } -} -template -Value deserialize(const json::Value& object) { - if constexpr(std::is_same_v) { - return Value{object.getAsBoolean().value()}; - } else if constexpr(is_integral_v || is_enum_v) { - return Value(object.getAsInteger().value()); - } else if constexpr(std::is_floating_point_v) { - return object.getAsNumber().value(); - } else if constexpr(is_string_v) { - return Value{object.getAsString().value()}; - } else if constexpr(is_array_v) { - Value array; - for(const auto& element: *object.getAsArray()) { - array.push_back(deserialize(element)); + template + static V deserialize(const json::Value& value, Serdes&&... serdes) { + assert(value.kind() == json::Value::Array && "Expect array"); + V array; + for(std::size_t i = 0; i < N; ++i) { + array[i] = + json::deserialize((*value.getAsArray())[i], std::forward(serdes)...); } return array; - } else { - Value value; - refl::foreach(value, [&](llvm::StringRef name, auto& field) { - if(auto element = object.getAsObject()->get(name)) { - field = deserialize>(*element); - } - }); - return value; } -} +}; + +template +struct Serde> { + using V = std::vector; + + constexpr inline static bool state = stateful_serde; + + template + static json::Value serialize(const V& v, Serdes&&... serdes) { + json::Array array; + for(const auto& element: v) { + array.push_back(json::serialize(element, std::forward(serdes)...)); + } + return array; + } + + template + static V deserialize(const json::Value& value, Serdes&&... serdes) { + assert(value.kind() == json::Value::Array && "Expect array"); + V array; + for(const auto& element: *value.getAsArray()) { + array.emplace_back(json::deserialize(element, std::forward(serdes)...)); + } + return array; + } +}; + +template +struct Serde> { + using V = llvm::ArrayRef; + + constexpr inline static bool state = stateful_serde; + + /// Only support serialization. + template + static json::Value serialize(const V& v, Serdes&&... serdes) { + json::Array array; + for(const auto& element: v) { + array.push_back(json::serialize(element, std::forward(serdes)...)); + } + return array; + } +}; } // namespace clice::json diff --git a/include/Support/Reflection.h b/include/Support/Reflection.h deleted file mode 100644 index a0a0a3cb..00000000 --- a/include/Support/Reflection.h +++ /dev/null @@ -1,323 +0,0 @@ -#pragma once - -// support basic reflection through template meta programming -#include -#include -#include -#include - -#include - -namespace clice::impl { - -struct Any { - consteval Any(std::size_t); - - template - consteval operator T () const; -}; - -template -consteval auto test() { - return [](std::index_sequence) { - return requires { T{Any(I)...}; }; - }(std::make_index_sequence{}); -} - -template -consteval auto member_count() { - if constexpr(test() && !test()) { - return N; - } else { - return member_count(); - } -} - -template -struct Wrapper { - T value; - - constexpr Wrapper(T value) : value(value) {} -}; - -template -consteval auto member_name() { - std::string_view name = std::source_location::current().function_name(); -#if __GNUC__ && (!__clang__) && (!_MSC_VER) - std::size_t start = name.rfind("::") + 2; - std::size_t end = name.rfind(')'); - return name.substr(start, end - start); -#elif __clang__ - std::size_t start = name.rfind(".") + 1; - std::size_t end = name.rfind('}'); - return name.substr(start, end - start); -#elif _MSC_VER - std::size_t start = name.rfind("->") + 2; - std::size_t end = name.rfind('}'); - return name.substr(start, end - start); -#else - static_assert(false, "Not supported compiler"); -#endif -} - -template -constexpr auto collcet_members(Object&& object) { - // clang-format off - if constexpr(count == 0) { - return std::tuple{}; - } else if constexpr(count == 1) { - auto&& [a] = object; - return std::tuple{&a}; - } else if constexpr(count == 2) { - auto&& [a, b] = object; - return std::tuple{&a, &b}; - } else if constexpr(count == 3) { - auto&& [a, b, c] = object; - return std::tuple{&a, &b, &c}; - } else if constexpr(count == 4) { - auto&& [a, b, c, d] = object; - return std::tuple{&a, &b, &c, &d}; - } else if constexpr(count == 5) { - auto&& [a, b, c, d, e] = object; - return std::tuple{&a, &b, &c, &d, &e}; - } else if constexpr(count == 6) { - auto&& [a, b, c, d, e, f] = object; - return std::tuple{&a, &b, &c, &d, &e, &f}; - } else if constexpr(count == 7) { - auto&& [a, b, c, d, e, f, g] = object; - return std::tuple{&a, &b, &c, &d, &e, &f, &g}; - } else if constexpr(count == 8) { - auto&& [a, b, c, d, e, f, g, h] = object; - return std::tuple{&a, &b, &c, &d, &e, &f, &g, &h}; - } else if constexpr(count == 9) { - auto&& [a, b, c, d, e, f, g, h, i] = object; - return std::tuple{&a, &b, &c, &d, &e, &f, &g, &h, &i}; - } else if constexpr(count == 10) { - auto&& [a, b, c, d, e, f, g, h, i, j] = object; - return std::tuple{&a, &b, &c, &d, &e, &f, &g, &h, &i, &j}; - } else if constexpr(count == 11) { - auto&& [a, b, c, d, e, f, g, h, i, j, k] = object; - return std::tuple{&a, &b, &c, &d, &e, &f, &g, &h, &i, &j, &k}; - } else if constexpr(count == 12) { - auto&& [a, b, c, d, e, f, g, h, i, j, k, l] = object; - return std::tuple{&a, &b, &c, &d, &e, &f, &g, &h, &i, &j, &k, &l}; - } else if constexpr(count == 13) { - auto&& [a, b, c, d, e, f, g, h, i, j, k, l, m] = object; - return std::tuple{&a, &b, &c, &d, &e, &f, &g, &h, &i, &j, &k, &l, &m}; - } else if constexpr(count == 14) { - auto&& [a, b, c, d, e, f, g, h, i, j, k, l, m, n] = object; - return std::tuple{&a, &b, &c, &d, &e, &f, &g, &h, &i, &j, &k, &l, &m, &n}; - } else if constexpr(count == 15) { - auto&& [a, b, c, d, e, f, g, h, i, j, k, l, m, n, o] = object; - return std::tuple{&a, &b, &c, &d, &e, &f, &g, &h, &i, &j, &k, &l, &m, &n, &o}; - } else if constexpr(count == 16) { - auto&& [a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p] = object; - return std::tuple{&a, &b, &c, &d, &e, &f, &g, &h, &i, &j, &k, &l, &m, &n, &o, &p}; - } else if constexpr(count == 17) { - auto&& [a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q] = object; - return std::tuple{&a, &b, &c, &d, &e, &f, &g, &h, &i, &j, &k, &l, &m, &n, &o, &p, &q}; - } else if constexpr(count == 18) { - auto&& [a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r] = object; - return std::tuple{&a, &b, &c, &d, &e, &f, &g, &h, &i, &j, &k, &l, &m, &n, &o, &p, &q, &r}; - } else if constexpr(count == 19) { - auto&& [a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s] = object; - return std::tuple{&a, &b, &c, &d, &e, &f, &g, &h, &i, &j, &k, &l, &m, &n, &o, &p, &q, &r, &s}; - } else if constexpr(count == 20) { - auto&& [a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t] = object; - return std::tuple{&a, &b, &c, &d, &e, &f, &g, &h, &i, &j, &k, &l, &m, &n, &o, &p, &q, &r, &s, &t}; - } else if constexpr(count == 21) { - auto&& [a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u] = object; - return std::tuple{&a, &b, &c, &d, &e, &f, &g, &h, &i, &j, &k, &l, &m, &n, &o, &p, &q, &r, &s, &t, &u}; - } else if constexpr(count == 22) { - auto&& [a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v] = object; - return std::tuple{&a, &b, &c, &d, &e, &f, &g, &h, &i, &j, &k, &l, &m, &n, &o, &p, &q, &r, &s, &t, &u, &v}; - } else if constexpr(count == 23) { - auto&& [a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w] = object; - return std::tuple{&a, &b, &c, &d, &e, &f, &g, &h, &i, &j, &k, &l, &m, &n, &o, &p, &q, &r, &s, &t, &u, &v, &w}; - } else if constexpr(count == 24) { - auto&& [a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x] = object; - return std::tuple{&a, &b, &c, &d, &e, &f, &g, &h, &i, &j, &k, &l, &m, &n, &o, &p, &q, &r, &s, &t, &u, &v, &w, &x}; - } else if constexpr(count == 25) { - auto&& [a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y] = object; - return std::tuple{&a, &b, &c, &d, &e, &f, &g, &h, &i, &j, &k, &l, &m, &n, &o, &p, &q, &r, &s, &t, &u, &v, &w, &x, &y}; - } else if constexpr(count == 26) { - auto&& [a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y, z] = object; - return std::tuple{&a, &b, &c, &d, &e, &f, &g, &h, &i, &j, &k, &l, &m, &n, &o, &p, &q, &r, &s, &t, &u, &v, &w, &x, &y, &z}; - } else if constexpr(count == 27) { - auto&& [a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y, z, _0] = object; - return std::tuple{&a, &b, &c, &d, &e, &f, &g, &h, &i, &j, &k, &l, &m, &n, &o, &p, &q, &r, &s, &t, &u, &v, &w, &x, &y, &z, &_0}; - } else if constexpr(count == 28) { - auto&& [a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y, z, _0, _1] = object; - return std::tuple{&a, &b, &c, &d, &e, &f, &g, &h, &i, &j, &k, &l, &m, &n, &o, &p, &q, &r, &s, &t, &u, &v, &w, &x, &y, &z, &_0, &_1}; - } else if constexpr(count == 29) { - auto&& [a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y, z, _0, _1, _2] = object; - return std::tuple{&a, &b, &c, &d, &e, &f, &g, &h, &i, &j, &k, &l, &m, &n, &o, &p, &q, &r, &s, &t, &u, &v, &w, &x, &y, &z, &_0, &_1, &_2}; - } else if constexpr(count == 30) { - auto&& [a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y, z, _0, _1, _2, _3] = object; - return std::tuple{&a, &b, &c, &d, &e, &f, &g, &h, &i, &j, &k, &l, &m, &n, &o, &p, &q, &r, &s, &t, &u, &v, &w, &x, &y, &z, &_0, &_1, &_2, &_3}; - } else { - static_assert(count <= 30, "Not supported member count"); - } - // clang-format on -} - -template -struct Storage { - inline static T value; -}; - -template -consteval auto enum_name() { - std::string_view name = std::source_location::current().function_name(); -#if __GNUC__ || __clang__ - std::size_t start = name.find('=') + 2; - std::size_t end = name.size() - 1; -#elif _MSC_VER - std::size_t start = name.find('<') + 1; - std::size_t end = name.rfind(">("); -#else - static_assert(false, "Not supported compiler"); -#endif - name = name.substr(start, end - start); - start = name.rfind("::"); - return start == std::string_view::npos ? name : name.substr(start + 2); -} - -template -consteval auto enum_max() { - constexpr auto value = std::bit_cast(static_cast>(N)); - if constexpr(enum_name().find(")") == std::string_view::npos) - return enum_max(); - else - return N; -} - -} // namespace clice::impl - -namespace clice::refl { - -template -struct Record; - -template -constexpr inline bool is_record_v = false; - -template -constexpr inline bool is_record_v> = true; - -#define CLICE_RECORD(name, ...) \ - struct name##Body; \ - using name = clice::refl::Record<__VA_ARGS__, name##Body>; \ - struct name##Body - -template -concept Reflectable = - std::is_aggregate_v> && std::is_default_constructible_v>; - -template -constexpr void foreach(Object&& object, const Callback& callback) { - using T = std::decay_t; - if constexpr(is_record_v) { - T::foreach(std::forward(object), callback); - } else { - constexpr auto count = impl::member_count(); - auto members = impl::collcet_members(object); - constexpr auto static_members = impl::collcet_members(impl::Storage::value); - [&](std::index_sequence) { - (callback(impl::member_name(static_members)>(), *std::get(members)), - ...); - }(std::make_index_sequence{}); - } -} - -// reflectable struct definition -template -struct Record : Ts... { - template - static void foreach(Object&& object, const Callback& callback) { - (clice::refl::foreach(static_cast>(object), callback), ...); - } -}; - -template -constexpr void walk(Object&& object, const Callback& callback) { - clice::refl::foreach(object, [&](std::string_view name, Field& field) { - if constexpr(Reflectable) { - walk(field, callback); - } else { - callback(name, field); - } - }); -} - -template - requires std::is_enum_v -constexpr auto enum_name(T value) { - constexpr auto count = impl::enum_max(); - constexpr auto names = [](std::index_sequence) { - return std::array{impl::enum_name(Is)>()...}; - }(std::make_index_sequence{}); - return names[static_cast(value)]; -} - -/// Invoke callback for each member of lhs and rhs, return false -/// in callback to abort the iteration. Return true if all members are visited. -template -constexpr bool foreach(LHS&& lhs, RHS&& rhs, const Callback& callback) { - constexpr auto lcount = impl::member_count>(); - constexpr auto rcount = impl::member_count>(); - static_assert(lcount == rcount, "Member count mismatch"); - auto lmembers = impl::collcet_members(lhs); - auto rmembers = impl::collcet_members(rhs); - - auto wrapper = [&](auto& lhs, auto& rhs) { - if constexpr(std::is_same_v) { - callback(lhs, rhs); - return true; - } else { - return callback(lhs, rhs); - } - }; - - return [&](std::index_sequence) { - /// use && for short-circuit evaluation. - return (wrapper(*std::get(lmembers), *std::get(rmembers)) && ...); - }(std::make_index_sequence{}); -} - -constexpr auto equal(const auto& lhs, const auto& rhs) { - if constexpr(requires { lhs == rhs; }) { - return (lhs == rhs); - } else { - return refl::foreach(lhs, rhs, [](const auto& lhs, const auto& rhs) { - return refl::equal(lhs, rhs); - }); - } -} - -/// Compare lhs and rhs according to the dictionary order of their members. -constexpr auto less(const auto& lhs, const auto& rhs) { - if constexpr(requires { lhs < rhs; }) { - return (lhs < rhs); - } else { - bool result = false; - refl::foreach(lhs, rhs, [&result](const auto& lhs, const auto& rhs) { - /// if lhs less than rhs, abort the iteration and return true. - if(refl::less(lhs, rhs)) { - result = true; - return false; - } - - /// if lhs equal to rhs, continue to next member. - if(refl::equal(lhs, rhs)) { - return true; - } - - /// if lhs greater than rhs, abort the iteration and return false. - return false; - }); - return result; - } -} - -}; // namespace clice::refl diff --git a/include/Support/Struct.h b/include/Support/Struct.h new file mode 100644 index 00000000..813416b4 --- /dev/null +++ b/include/Support/Struct.h @@ -0,0 +1,287 @@ +#pragma once + +#include +#include +#include +#include + +#include "JSON.h" +#include "Format.h" + +namespace clice::support { + +namespace impl { + +struct Any { + consteval Any(std::size_t); + + template + consteval operator T () const; +}; + +template +consteval auto test() { + return [](std::index_sequence) { + return requires { T{Any(I)...}; }; + }(std::make_index_sequence{}); +} + +template +consteval auto member_count() { + if constexpr(test() && !test()) { + return N; + } else { + return member_count(); + } +} + +template +struct wrapper { + T value; + + constexpr wrapper(T value) : value(value) {} +}; + +template +struct storage { + inline static T value; +}; + +template +consteval auto member_name() { + std::string_view name = std::source_location::current().function_name(); +#if __GNUC__ && (!__clang__) && (!_MSC_VER) + std::size_t start = name.rfind("::") + 2; + std::size_t end = name.rfind(')'); + return name.substr(start, end - start); +#elif __clang__ + std::size_t start = name.rfind(".") + 1; + std::size_t end = name.rfind('}'); + return name.substr(start, end - start); +#elif _MSC_VER + std::size_t start = name.rfind("->") + 2; + std::size_t end = name.rfind('}'); + return name.substr(start, end - start); +#else + static_assert(false, "Not supported compiler"); +#endif +} + +} // namespace impl + +template +struct Struct { + constexpr inline static bool reflectable = + std::is_aggregate_v> && + std::is_default_constructible_v>; + + constexpr static std::size_t member_count() { + return impl::member_count(); + } + + constexpr static T& instance() { + return impl::storage::value; + } + + template + constexpr static auto collcet_members(Object&& object) { + // clang-format off + constexpr std::size_t count = member_count(); + if constexpr(count == 0) { + return std::tuple{}; + } else if constexpr(count == 1) { + auto&& [a] = object; + return std::tuple{&a}; + } else if constexpr(count == 2) { + auto&& [a, b] = object; + return std::tuple{&a, &b}; + } else if constexpr(count == 3) { + auto&& [a, b, c] = object; + return std::tuple{&a, &b, &c}; + } else if constexpr(count == 4) { + auto&& [a, b, c, d] = object; + return std::tuple{&a, &b, &c, &d}; + } else if constexpr(count == 5) { + auto&& [a, b, c, d, e] = object; + return std::tuple{&a, &b, &c, &d, &e}; + } else if constexpr(count == 6) { + auto&& [a, b, c, d, e, f] = object; + return std::tuple{&a, &b, &c, &d, &e, &f}; + } else if constexpr(count == 7) { + auto&& [a, b, c, d, e, f, g] = object; + return std::tuple{&a, &b, &c, &d, &e, &f, &g}; + } else if constexpr(count == 8) { + auto&& [a, b, c, d, e, f, g, h] = object; + return std::tuple{&a, &b, &c, &d, &e, &f, &g, &h}; + } else if constexpr(count == 9) { + auto&& [a, b, c, d, e, f, g, h, i] = object; + return std::tuple{&a, &b, &c, &d, &e, &f, &g, &h, &i}; + } else if constexpr(count == 10) { + auto&& [a, b, c, d, e, f, g, h, i, j] = object; + return std::tuple{&a, &b, &c, &d, &e, &f, &g, &h, &i, &j}; + } else if constexpr(count == 11) { + auto&& [a, b, c, d, e, f, g, h, i, j, k] = object; + return std::tuple{&a, &b, &c, &d, &e, &f, &g, &h, &i, &j, &k}; + } else if constexpr(count == 12) { + auto&& [a, b, c, d, e, f, g, h, i, j, k, l] = object; + return std::tuple{&a, &b, &c, &d, &e, &f, &g, &h, &i, &j, &k, &l}; + } else if constexpr(count == 13) { + auto&& [a, b, c, d, e, f, g, h, i, j, k, l, m] = object; + return std::tuple{&a, &b, &c, &d, &e, &f, &g, &h, &i, &j, &k, &l, &m}; + } else if constexpr(count == 14) { + auto&& [a, b, c, d, e, f, g, h, i, j, k, l, m, n] = object; + return std::tuple{&a, &b, &c, &d, &e, &f, &g, &h, &i, &j, &k, &l, &m, &n}; + } else if constexpr(count == 15) { + auto&& [a, b, c, d, e, f, g, h, i, j, k, l, m, n, o] = object; + return std::tuple{&a, &b, &c, &d, &e, &f, &g, &h, &i, &j, &k, &l, &m, &n, &o}; + } else if constexpr(count == 16) { + auto&& [a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p] = object; + return std::tuple{&a, &b, &c, &d, &e, &f, &g, &h, &i, &j, &k, &l, &m, &n, &o, &p}; + } else if constexpr(count == 17) { + auto&& [a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q] = object; + return std::tuple{&a, &b, &c, &d, &e, &f, &g, &h, &i, &j, &k, &l, &m, &n, &o, &p, &q}; + } else if constexpr(count == 18) { + auto&& [a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r] = object; + return std::tuple{&a, &b, &c, &d, &e, &f, &g, &h, &i, &j, &k, &l, &m, &n, &o, &p, &q, &r}; + } else if constexpr(count == 19) { + auto&& [a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s] = object; + return std::tuple{&a, &b, &c, &d, &e, &f, &g, &h, &i, &j, &k, &l, &m, &n, &o, &p, &q, &r, &s}; + } else if constexpr(count == 20) { + auto&& [a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t] = object; + return std::tuple{&a, &b, &c, &d, &e, &f, &g, &h, &i, &j, &k, &l, &m, &n, &o, &p, &q, &r, &s, &t}; + } else if constexpr(count == 21) { + auto&& [a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u] = object; + return std::tuple{&a, &b, &c, &d, &e, &f, &g, &h, &i, &j, &k, &l, &m, &n, &o, &p, &q, &r, &s, &t, &u}; + } else if constexpr(count == 22) { + auto&& [a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v] = object; + return std::tuple{&a, &b, &c, &d, &e, &f, &g, &h, &i, &j, &k, &l, &m, &n, &o, &p, &q, &r, &s, &t, &u, &v}; + } else if constexpr(count == 23) { + auto&& [a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w] = object; + return std::tuple{&a, &b, &c, &d, &e, &f, &g, &h, &i, &j, &k, &l, &m, &n, &o, &p, &q, &r, &s, &t, &u, &v, &w}; + } else if constexpr(count == 24) { + auto&& [a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x] = object; + return std::tuple{&a, &b, &c, &d, &e, &f, &g, &h, &i, &j, &k, &l, &m, &n, &o, &p, &q, &r, &s, &t, &u, &v, &w, &x}; + } else if constexpr(count == 25) { + auto&& [a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y] = object; + return std::tuple{&a, &b, &c, &d, &e, &f, &g, &h, &i, &j, &k, &l, &m, &n, &o, &p, &q, &r, &s, &t, &u, &v, &w, &x, &y}; + } else if constexpr(count == 26) { + auto&& [a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y, z] = object; + return std::tuple{&a, &b, &c, &d, &e, &f, &g, &h, &i, &j, &k, &l, &m, &n, &o, &p, &q, &r, &s, &t, &u, &v, &w, &x, &y, &z}; + } else if constexpr(count == 27) { + auto&& [a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y, z, _0] = object; + return std::tuple{&a, &b, &c, &d, &e, &f, &g, &h, &i, &j, &k, &l, &m, &n, &o, &p, &q, &r, &s, &t, &u, &v, &w, &x, &y, &z, &_0}; + } else if constexpr(count == 28) { + auto&& [a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y, z, _0, _1] = object; + return std::tuple{&a, &b, &c, &d, &e, &f, &g, &h, &i, &j, &k, &l, &m, &n, &o, &p, &q, &r, &s, &t, &u, &v, &w, &x, &y, &z, &_0, &_1}; + } else if constexpr(count == 29) { + auto&& [a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y, z, _0, _1, _2] = object; + return std::tuple{&a, &b, &c, &d, &e, &f, &g, &h, &i, &j, &k, &l, &m, &n, &o, &p, &q, &r, &s, &t, &u, &v, &w, &x, &y, &z, &_0, &_1, &_2}; + } else if constexpr(count == 30) { + auto&& [a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y, z, _0, _1, _2, _3] = object; + return std::tuple{&a, &b, &c, &d, &e, &f, &g, &h, &i, &j, &k, &l, &m, &n, &o, &p, &q, &r, &s, &t, &u, &v, &w, &x, &y, &z, &_0, &_1, &_2, &_3}; + } else { + static_assert(count <= 30, "Not supported member count"); + } + // clang-format on + } +}; + +/// To check if the type is reflectable. +template +concept reflectable = Struct::reflectable; + +/// Turn the return value of the callable to bool. +template +constexpr auto foldable(const Callable& callable) { + return [&](auto&&... args) { + using Ret = std::invoke_result_t; + if constexpr(std::is_void_v) { + callable(args...); + return true; + } else { + return bool(callable(args...)); + } + }; +} + +template +constexpr bool foreach(Object&& object, const Callback& callable) { + using S = Struct>; + constexpr auto count = S::member_count(); + auto members = S::collcet_members(object); + constexpr auto static_members = S::collcet_members(S::instance()); + return [&](std::index_sequence) { + return (foldable(callable)(impl::member_name(static_members)>(), + *std::get(members)) && + ...); + }(std::make_index_sequence{}); +} + +/// Invoke callback for each member of lhs and rhs, return false +/// in callback to abort the iteration. Return true if all members are visited. +template +constexpr bool foreach(LHS&& lhs, RHS&& rhs, const Callback& callback) { + using L = Struct>; + using R = Struct>; + static_assert(L::member_count() == R::member_count(), "Member count mismatch"); + return [&](std::index_sequence) { + return (foldable(callback)(*std::get(L::collcet_members(lhs)), + *std::get(R::collcet_members(rhs))) && + ...); + }(std::make_index_sequence{}); +} + +} // namespace clice::support + +namespace clice::json { + +template +struct Serde { + constexpr inline static bool state = + !support::foreach(support::Struct::instance(), [](auto, auto&& member) { + return !json::stateful_serde>; + }); + + template + static json::Value serialize(const T& t, Serdes&&... serdes) { + json::Object object; + support::foreach(t, [&](std::string_view name, auto&& member) { + object.try_emplace(llvm::StringRef(name), + json::serialize(member, std::forward(serdes)...)); + }); + return object; + } + + template + static T deserialize(const json::Value& value, Serdes&&... serdes) { + assert(value.kind() == json::Value::Object && "Expect an object"); + auto object = value.getAsObject(); + T t; + support::foreach(t, [&](std::string_view name, auto&& member) { + auto v = object->get(llvm::StringRef(name)); + assert(v && "Member not found"); + member = json::deserialize>( + *v, + std::forward(serdes)...); + }); + return t; + } +}; + +} // namespace clice::json + +/// FIXME: +// template +// struct std::formatter : std::formatter { +// using Base = std::formatter; +// +// template +// constexpr auto parse(ParseContext& ctx) { +// return Base::parse(ctx); +// } +// +// template +// auto format(const T& t, FormatContext& ctx) { +// return Base::format(clice::json::serialize(t), ctx); +// } +// }; diff --git a/include/Support/Support.h b/include/Support/Support.h new file mode 100644 index 00000000..60c74709 --- /dev/null +++ b/include/Support/Support.h @@ -0,0 +1,10 @@ +#pragma once + +#include "ADT.h" +#include "JSON.h" +#include "Format.h" +#include "Enum.h" +#include "Struct.h" +#include "Compare.h" +#include "FileSystem.h" + diff --git a/include/Support/TypeTraits.h b/include/Support/TypeTraits.h index d37ff41d..9424dc8b 100644 --- a/include/Support/TypeTraits.h +++ b/include/Support/TypeTraits.h @@ -59,4 +59,12 @@ constexpr bool is_specialization_of, HKT> = true; template constexpr inline bool dependent_false = false; +template +concept integral = + std::is_integral_v && !std::is_same_v && !std::is_same_v && + !std::is_same_v && !std::is_same_v && !std::is_same_v; + +template +concept floating_point = std::is_floating_point_v; + } // namespace clice diff --git a/src/Compiler/Diagnostic.cpp b/src/Compiler/Diagnostic.cpp index 9029625f..3f4cbe7b 100644 --- a/src/Compiler/Diagnostic.cpp +++ b/src/Compiler/Diagnostic.cpp @@ -1,4 +1,4 @@ -#include +#include #include #include diff --git a/src/Compiler/Directive.cpp b/src/Compiler/Directive.cpp index 86b0fe0c..bf942302 100644 --- a/src/Compiler/Directive.cpp +++ b/src/Compiler/Directive.cpp @@ -1,5 +1,5 @@ +#include #include -#include #include #include @@ -20,7 +20,9 @@ struct CommentHandler : public clang::CommentHandler { }; struct PragmaHandler : public clang::PragmaHandler { - virtual void HandlePragma(clang::Preprocessor& PP, clang::PragmaIntroducer Introducer, clang::Token& FirstToken) { + virtual void HandlePragma(clang::Preprocessor& PP, + clang::PragmaIntroducer Introducer, + clang::Token& FirstToken) { // TODO: } @@ -32,7 +34,8 @@ struct PragmaHandler : public clang::PragmaHandler { }; struct PPCallback : clang::PPCallbacks { - PPCallback(clang::Preprocessor& preproc) : preproc(preproc), srcMgr(preproc.getSourceManager()) {} + PPCallback(clang::Preprocessor& preproc) : + preproc(preproc), srcMgr(preproc.getSourceManager()) {} clang::Preprocessor& preproc; clang::SourceManager& srcMgr; @@ -62,7 +65,8 @@ struct PPCallback : clang::PPCallbacks { // llvm::outs() << RealPath << "\n"; } - void PragmaDirective(clang::SourceLocation Loc, clang::PragmaIntroducerKind Introducer) override { + void PragmaDirective(clang::SourceLocation Loc, + clang::PragmaIntroducerKind Introducer) override { // llvm::outs() << "PragmaDirective\n"; } @@ -77,14 +81,17 @@ struct PPCallback : clang::PPCallbacks { clang::PPCallbacks::ConditionValueKind conditionValue, clang::SourceLocation ifLoc) override {} - void Ifdef(clang::SourceLocation loc, const clang::Token& name, const clang::MacroDefinition& definition) override { - } + void Ifdef(clang::SourceLocation loc, + const clang::Token& name, + const clang::MacroDefinition& definition) override {} void Elifdef(clang::SourceLocation loc, const clang::Token& name, const clang::MacroDefinition& definition) override {} - void Elifdef(clang::SourceLocation loc, clang::SourceRange conditionRange, clang::SourceLocation ifLoc) override {} + void Elifdef(clang::SourceLocation loc, + clang::SourceRange conditionRange, + clang::SourceLocation ifLoc) override {} void Ifndef(clang::SourceLocation loc, const clang::Token& name, @@ -96,7 +103,9 @@ struct PPCallback : clang::PPCallbacks { const clang::MacroDefinition& definition) override {} // invoke when #elifndef is skipped - void Elifndef(clang::SourceLocation loc, clang::SourceRange conditionRange, clang::SourceLocation ifLoc) override {} + void Elifndef(clang::SourceLocation loc, + clang::SourceRange conditionRange, + clang::SourceLocation ifLoc) override {} void Else(clang::SourceLocation loc, clang::SourceLocation ifLoc) override {} @@ -108,7 +117,8 @@ struct PPCallback : clang::PPCallbacks { // llvm::outs() << "is builtin: " << info->isBuiltinMacro() << "\n"; if(!info->isBuiltinMacro()) { auto location = MacroNameTok.getLocation(); - if(!srcMgr.isWrittenInBuiltinFile(location) && !srcMgr.isWrittenInCommandLineFile(location)) { + if(!srcMgr.isWrittenInBuiltinFile(location) && + !srcMgr.isWrittenInCommandLineFile(location)) { MacroNameTok.getLocation().dump(srcMgr); // srcMgr.getIncludeLoc(srcMgr.getFileID(MacroNameTok.getLocation())).dump(srcMgr); llvm::outs() << preproc.getSpelling(MacroNameTok) << "\n"; diff --git a/src/Feature/CodeCompletion.cpp b/src/Feature/CodeCompletion.cpp index 0248b7d6..288c92ba 100644 --- a/src/Feature/CodeCompletion.cpp +++ b/src/Feature/CodeCompletion.cpp @@ -7,44 +7,92 @@ namespace clice::feature { namespace { +struct CompletionPrefix { + // The unqualified partial name. + // If there is none, begin() == end() == completion position. + llvm::StringRef name; + + // The spelled scope qualifier, such as Foo::. + // If there is none, begin() == end() == name.begin(). + llvm::StringRef qualifier; + + static CompletionPrefix from(llvm::StringRef content, std::size_t offset) { + assert(offset <= content.size()); + CompletionPrefix result; + + llvm::StringRef rest = content.take_front(offset); + + // Consume the unqualified name. We only handle ASCII characters. + // isAsciiIdentifierContinue will let us match "0invalid", but we don't mind. + while(!rest.empty() && clang::isAsciiIdentifierContinue(rest.back())) { + rest = rest.drop_back(); + } + + result.name = content.slice(rest.size(), offset); + + // Consume qualifiers. + while(rest.consume_back("::") && !rest.ends_with(":")) { + // reject :::: + while(!rest.empty() && clang::isAsciiIdentifierContinue(rest.back())) { + rest = rest.drop_back(); + } + } + + result.qualifier = content.slice(rest.size(), result.name.begin() - content.begin()); + return result; + } +}; + +proto::CompletionItemKind kindForDecl(const clang::NamedDecl* decl){ + +} + class CodeCompletionCollector final : public clang::CodeCompleteConsumer { public: - CodeCompletionCollector(proto::CompletionResult& completions, uint32_t line, uint32_t column) : + CodeCompletionCollector(proto::CompletionResult& completions, + uint32_t line, + uint32_t column, + llvm::StringRef content) : clang::CodeCompleteConsumer({}), completions(completions), allocator(new clang::GlobalCodeCompletionAllocator()), info(allocator), line(line), - column(column) {} + column(column), content(content) {} void ProcessCodeCompleteResults(clang::Sema& sema, clang::CodeCompletionContext context, clang::CodeCompletionResult* results, unsigned count) final { auto loc = sema.getPreprocessor().getCodeCompletionLoc(); + auto offset = sema.getSourceManager().getFileOffset(loc); + auto prefix = CompletionPrefix::from(content, offset); + for(auto& result: llvm::make_range(results, results + count)) { - auto& completion = completions.emplace_back(); + auto& item = completions.emplace_back(); + item.kind = proto::CompletionItemKind::Text; switch(result.Kind) { case clang::CodeCompletionResult::RK_Declaration: { - completion.label = result.Declaration->getDeclName().getAsString(); + item.label = result.Declaration->getName(); break; } case clang::CodeCompletionResult::RK_Keyword: { - completion.label = result.Keyword; + item.label = result.Keyword; + item.kind = proto::CompletionItemKind::Keyword; break; } case clang::CodeCompletionResult::RK_Macro: { - completion.label = result.Macro->getName(); + item.label = result.Macro->getName(); break; } case clang::CodeCompletionResult::RK_Pattern: { - completion.label = result.Pattern->getTypedText(); + item.kind = proto::CompletionItemKind::Snippet; + item.label = result.Pattern->getTypedText(); break; } } - completion.textEdit.newText = completion.label; - completion.textEdit.range = { - .start = {line - 1, column - 1 }, - .end = {line - 1, static_cast(column + completion.label.size()) - 1}, + item.textEdit.newText = item.label; + item.textEdit.range = { + .start = {line - 1, static_cast(column - 1 - prefix.name.size())}, + .end = {line - 1, static_cast(column + item.label.size()) - 1 }, }; - completion.kind = proto::CompletionItemKind::Text; } } @@ -57,11 +105,12 @@ public: } private: + uint32_t line; + uint32_t column; + llvm::StringRef content; std::shared_ptr allocator; clang::CodeCompletionTUInfo info; proto::CompletionResult& completions; - uint32_t line; - uint32_t column; }; } // namespace @@ -79,15 +128,15 @@ json::Value capability(json::Value clientCapabilities) { }; } -proto::CompletionResult codeCompletion(CompliationParams& compliation, +proto::CompletionResult codeCompletion(CompliationParams& params, uint32_t line, uint32_t column, llvm::StringRef file, const config::CodeCompletionOption& option) { proto::CompletionResult completions; - auto consumer = new CodeCompletionCollector(completions, line, column); + auto consumer = new CodeCompletionCollector(completions, line, column, params.content); - auto info = codeCompleteAt(compliation, line, column, file, consumer); + auto info = codeCompleteAt(params, line, column, file, consumer); if(info) { return completions; } else { diff --git a/src/Index/Serialize.cpp b/src/Index/Serialize.cpp index b205fe37..97ba42f9 100644 --- a/src/Index/Serialize.cpp +++ b/src/Index/Serialize.cpp @@ -1,4 +1,5 @@ #include +#include namespace clice::index { @@ -56,9 +57,7 @@ public: if constexpr(requires { out = in; }) { out = in; } else { - refl::foreach(in, out, [&](const auto& in, U& out) { - pack(in, out); - }); + support::foreach(in, out, [&](const auto& in, U& out) { pack(in, out); }); } } diff --git a/src/Index/SymbolBuilder.h b/src/Index/SymbolBuilder.h index 77259856..03257771 100644 --- a/src/Index/SymbolBuilder.h +++ b/src/Index/SymbolBuilder.h @@ -120,14 +120,14 @@ public: SymbolProxy addDeclaration(LocationRef location) { RelationKind kind = RelationKind::Reference; - kind.set(RelationKinds::Declaration); + kind &= RelationKind::Declaration; return addRelation(kind, location); } SymbolProxy addDefinition(LocationRef location) { RelationKind kind = RelationKind::Reference; - kind.set(RelationKinds::Declaration); - kind.set(RelationKinds::Definition); + kind &= RelationKind::Declaration; + kind &= RelationKind::Definition; return addRelation(kind, location); } diff --git a/src/Server/Config.cpp b/src/Server/Config.cpp index b9b514ee..56b9138e 100644 --- a/src/Server/Config.cpp +++ b/src/Server/Config.cpp @@ -3,12 +3,7 @@ #include #include -#include -#include -#include - -#include "Support/Format.h" -#include "Support/JSON.h" +#include namespace clice::config { @@ -97,11 +92,11 @@ int parse(llvm::StringRef execute, llvm::StringRef filepath) { void init(std::string_view workplace) { predefined["workplace"] = workplace; - refl::walk(config, [&](std::string_view name, Field& field) { - if constexpr(std::is_same_v) { - field = replace(field); - } - }); + // refl::walk(config, [&](std::string_view name, Field& field) { + // if constexpr(std::is_same_v) { + // field = replace(field); + // } + // }); log::info("Config initialized successfully, result: {0}", json::serialize(config)); return; diff --git a/src/Server/Scheduler.cpp b/src/Server/Scheduler.cpp index 80b65045..eb60edaf 100644 --- a/src/Server/Scheduler.cpp +++ b/src/Server/Scheduler.cpp @@ -121,7 +121,7 @@ async::promise Scheduler::buildAST(llvm::StringRef filepath, llvm::StringR file.content = content; file.compiler = std::move(compiler); - log::info("Build AST successfully for {0}, elapsed {1}ms", filepath, tracer.duration()); + log::info("Build AST successfully for {0}, elapsed {1}", filepath, tracer.duration()); if(!file.waitings.empty()) { auto task = std::move(file.waitings.front()); @@ -179,7 +179,7 @@ async::promise Scheduler::codeComplete(llvm::StringRef auto result = co_await async::schedule_task(std::move(task)); - log::info("Code completion for {0} is done, elapsed {1}ms", filepath, tracer.duration()); + log::info("Code completion for {0} is done, elapsed {1}", filepath, tracer.duration()); co_return result; } diff --git a/unittests/Feature/CodeCompletion.cpp b/unittests/Feature/CodeCompletion.cpp new file mode 100644 index 00000000..29089c0b --- /dev/null +++ b/unittests/Feature/CodeCompletion.cpp @@ -0,0 +1,39 @@ +#include "../Test.h" +#include "Compiler/Compiler.h" +#include "Support/Support.h" + +#include "Feature/CodeCompletion.h" + +namespace { + +using namespace clice; + +TEST(Feature, codeCompletion) { + const char* code = R"cpp( +int foo = 2; +)cpp"; + + llvm::SmallVector compileArgs = { + "clang++", + "-std=c++20", + "main.cpp", + "-resource-dir", + "/home/ykiko/C++/clice2/build/lib/clang/20", + }; + + CompliationParams params; + params.path = "main.cpp"; + params.content = code; + params.args = compileArgs; + + auto result = feature::codeCompletion(params, 2, 2, "main.cpp", {}); + for(auto& item: result) { + llvm::outs() << std::format("kind: {}, label: {}, range: {}\n", + support::enum_name(item.kind), + item.label, + json::serialize(item.textEdit.range)) + << "\n"; + } +} + +} // namespace diff --git a/unittests/Index/Tester.h b/unittests/Index/Tester.h index 1151ff0e..f5723597 100644 --- a/unittests/Index/Tester.h +++ b/unittests/Index/Tester.h @@ -5,6 +5,7 @@ #include "Compiler/Compiler.h" #include "Index/Loader.h" +#include "Support/Support.h" namespace clice { @@ -25,7 +26,7 @@ void testEqual(const Loader& loader, const In& in, const Out& out) { testEqual(loader, in[i], array[i]); } } else { - refl::foreach(in, out, [&](const auto& lhs, const auto& rhs) { + support::foreach(in, out, [&](const auto& lhs, const auto& rhs) { testEqual(loader, lhs, rhs); }); } diff --git a/unittests/Support/Enum.cpp b/unittests/Support/Enum.cpp index 79dae7b5..adce700d 100644 --- a/unittests/Support/Enum.cpp +++ b/unittests/Support/Enum.cpp @@ -2,62 +2,66 @@ #include namespace { + using namespace clice; -enum class ColorKinds { - Red, - Green, - Blue, - Yellow, -}; +struct Color : support::Enum { + using Enum::Enum; -struct ColorKind : enum_type { - using enum ColorKinds; - using enum_type::enum_type; + enum Kind : uint8_t { + Red, + Green, + Blue, + Yellow, + }; }; TEST(Support, normal_enum) { - ColorKind color = ColorKind::Red; + constexpr Color red = Color::Red; + constexpr Color green = Color::Green; + constexpr Color blue = Color::Blue; + constexpr Color yellow = Color::Yellow; - std::string string; - llvm::raw_string_ostream stream(string); - stream << color; - stream.flush(); - EXPECT_EQ(string, "[normal enum, value: 0, name: Red]"); + static_assert(red.name() == "Red" && red.value() == 0); + static_assert(green.name() == "Green" && green.value() == 1); + static_assert(blue.name() == "Blue" && blue.value() == 2); + static_assert(yellow.name() == "Yellow" && yellow.value() == 3); - EXPECT_EQ(underlying_value(color), 0); + static_assert(red != green && red != blue && red != yellow); + + constexpr Color red2 = Color(0); + static_assert(red == red2); } -enum class MaskKinds { - A = 0, - B, - C, - D, -}; +struct Mask : support::Enum { + using Enum::Enum; -struct MaskKind : enum_type { - using enum MaskKinds; - using enum_type::enum_type; + enum Kind { + A = 0, + B, + C, + D, + }; }; TEST(Support, mask_enum) { - MaskKind mask = MaskKind::A; - mask.set(MaskKind::B); - EXPECT_TRUE(mask.is(MaskKind::A)); - EXPECT_TRUE(mask.is(MaskKind::B)); - EXPECT_FALSE(mask.is(MaskKind::C)); - EXPECT_FALSE(mask.is(MaskKind::D)); - EXPECT_EQ(underlying_value(mask), 3); + constexpr Mask mask = Mask::A; + constexpr Mask mask2 = Mask::B; + constexpr Mask mask3 = Mask::C; + constexpr Mask mask4 = Mask::D; - std::string string; - llvm::raw_string_ostream stream(string); - stream << mask; - stream.flush(); - EXPECT_EQ(string, "[mask enum, value: 3, masks: A | B]"); + static_assert(mask.name() == "A" && mask.value() == 1); + static_assert(mask2.name() == "B" && mask2.value() == 2); + static_assert(mask3.name() == "C" && mask3.value() == 4); + static_assert(mask4.name() == "D" && mask4.value() == 8); - MaskKind mask2 = MaskKind::C; - EXPECT_FALSE(mask == mask2); + static_assert(mask != mask2 && mask != mask3 && mask != mask4); + + constexpr Mask mask5 = Mask(Mask::A, Mask::B, Mask::C, Mask::D); + static_assert(mask5.name() == "A | B | C | D" && mask5.value() == 15); } + + } // namespace diff --git a/unittests/Support/JSON.cpp b/unittests/Support/JSON.cpp index a09e8777..e8f46fc9 100644 --- a/unittests/Support/JSON.cpp +++ b/unittests/Support/JSON.cpp @@ -1,11 +1,11 @@ #include -#include +#include + +namespace clice { namespace { -using namespace clice; - -TEST(JSON, Point) { +TEST(Support, JSON) { json::Object object; object["x"] = 1; object["y"] = 2; @@ -24,3 +24,34 @@ TEST(JSON, Point) { } } // namespace + +struct ValueRef { + int index; +}; + +template <> +struct json::Serde { + constexpr inline static bool state = true; + + std::vector& decoder; + + json::Value serialize(const ValueRef& ref) { + return json::Value(decoder[ref.index]); + } +}; + +TEST(Support, StatefulSerde) { + std::vector refs; + refs.emplace_back(4); + refs.emplace_back(3); + refs.emplace_back(2); + refs.emplace_back(1); + refs.emplace_back(0); + + std::vector decoder = {1, 2, 3, 4, 5}; + json::Serde serde{decoder}; + auto result = json::serialize(refs, serde); + ASSERT_EQ(result, json::Value(json::Array{5, 4, 3, 2, 1})); +} + +} // namespace clice