diff --git a/include/dolores/handler.hpp b/include/dolores/handler.hpp index 0ef167f..7a7d108 100644 --- a/include/dolores/handler.hpp +++ b/include/dolores/handler.hpp @@ -13,27 +13,15 @@ #include "traits.hpp" namespace dolores { - template >> + template class Handler { + static_assert(is_derived_from_user_event_v); public: - explicit Handler(std::function ¤t)> func, std::shared_ptr matcher = nullptr) - : _func(std::move(func)), _matcher(std::move(matcher)) { - } - - bool match(const E &event, Session &session) const { - if (!_matcher) return true; - return _matcher->match(event, session); - } - - void run(Current ¤t) const { - if (!_func) return; - _func(current); - } - - private: - std::shared_ptr _matcher; - std::function ¤t)> _func; + virtual ~Handler() = 0; + virtual bool match(const E &event, Session &session) const = 0; + virtual void run(Current ¤t) = 0; }; + inline Handler::~Handler() = default; struct _HandlerVecWrapper { template >> @@ -80,6 +68,27 @@ namespace dolores { } } } + + template + class HandlerImplementation : public Handler { + static_assert(is_derived_from_user_event_v); + public: + explicit HandlerImplementation(Func func, AllMatcher matcher) + : _func(std::move(func)), _matcher(std::move(matcher)) { + } + + bool match(const E &event, Session &session) const override { + return _matcher(event, session); + } + + void run(Current ¤t) const override { + _func(current); + } + + private: + RunFunc _func; + AllMatcher _matcher; + }; } // namespace dolores #define _DOLORES_UNIQUE_NAME_2(Name, Number) Name##Number @@ -88,8 +97,8 @@ namespace dolores { #define _DOLORES_MAKE_HANDLER_2(EventType, FuncName, ...) \ static void FuncName(dolores::Current &); \ - static const auto FuncName##_res = dolores::add_handler(std::make_shared>( \ - FuncName, std::make_shared(__VA_ARGS__))); \ + static const auto FuncName##_res = dolores::add_handler(std::make_shared>( \ + FuncName, dolores::matchers::all(__VA_ARGS__))); \ static void FuncName(dolores::Current ¤t) #define _DOLORES_MAKE_HANDLER(EventType, FuncName, ...) _DOLORES_MAKE_HANDLER_2(EventType, FuncName, __VA_ARGS__) diff --git a/include/dolores/matcher.hpp b/include/dolores/matcher.hpp index c723b64..247ac6b 100644 --- a/include/dolores/matcher.hpp +++ b/include/dolores/matcher.hpp @@ -10,6 +10,7 @@ #include #include #include +#include #include "session.hpp" #include "string.hpp" @@ -17,308 +18,88 @@ #include "watashi.hpp" namespace dolores { - class MatcherBase { - public: - virtual bool match(const cq::MessageEvent &event, Session &session) const { - return match(static_cast(event), session); - } - - virtual bool match(const cq::NoticeEvent &event, Session &session) const { - return match(static_cast(event), session); - } - - virtual bool match(const cq::RequestEvent &event, Session &session) const { - return match(static_cast(event), session); - } - - virtual bool match(const cq::UserEvent &event, Session &session) const { - return false; + namespace matchers { + template >>> + constexpr auto operator!(T &&matcher) { + return [matcher](auto &&... args) { return !matcher(std::forward(args)...); }; } - }; - class MessageMatcher : virtual public MatcherBase { - public: - virtual bool match(const cq::Target &target, const std::string_view &message, Session &session) const { - return false; + template > && is_matcher_v>>> + constexpr auto operator&&(TL &&lhs, TR &&rhs) { + return [lhs, rhs](auto &&... args) { return lhs(args...) && rhs(args...); }; } - bool match(const cq::MessageEvent &event, Session &session) const final { - return match(event.target, event.message, session); + template > && is_matcher_v>>> + constexpr auto operator||(TL &&lhs, TR &&rhs) { + return [lhs, rhs](auto &&... args) { return lhs(args...) || rhs(args...); }; } - }; - - namespace matchers { - class _NotMatcher : public MatcherBase { - public: - template >> - explicit _NotMatcher(T &&matcher) : _matcher(std::make_shared>(std::forward(matcher))) { - } - - bool match(const cq::MessageEvent &event, Session &session) const override { - return _match(event, session); - } - - bool match(const cq::NoticeEvent &event, Session &session) const override { - return _match(event, session); - } - - bool match(const cq::RequestEvent &event, Session &session) const override { - return _match(event, session); - } - - bool match(const cq::UserEvent &event, Session &session) const override { - return _match(event, session); - } - - protected: - std::shared_ptr _matcher; - - template - bool _match(const E &event, Session &session) const { - return !_matcher->match(event, session); - } - }; - - class _NotMessageMatcher : public MessageMatcher { - public: - template >> - explicit _NotMessageMatcher(T &&matcher) - : _matcher(std::make_shared>(std::forward(matcher))) { - } - - bool match(const cq::Target &target, const std::string_view &message, Session &session) const override { - return !_matcher->match(target, message, session); - } - - protected: - std::shared_ptr _matcher; - }; - - class _AndMatcher : public MatcherBase { - public: - template >> - _AndMatcher(TL &&lhs, TR &&rhs) - : _lhs(std::make_shared>(std::forward(lhs))), - _rhs(std::make_shared>(std::forward(rhs))) { - } - - bool match(const cq::MessageEvent &event, Session &session) const override { - return _match(event, session); - } - - bool match(const cq::NoticeEvent &event, Session &session) const override { - return _match(event, session); - } - - bool match(const cq::RequestEvent &event, Session &session) const override { - return _match(event, session); - } - - bool match(const cq::UserEvent &event, Session &session) const override { - return _match(event, session); - } - - protected: - std::shared_ptr _lhs; - std::shared_ptr _rhs; - - template - bool _match(const E &event, Session &session) const { - return _lhs->match(event, session) && _rhs->match(event, session); - } - }; - - class _AndMessageMatcher : public MessageMatcher { - public: - template >> - _AndMessageMatcher(TL &&lhs, TR &&rhs) - : _lhs(std::make_shared>(std::forward(lhs))), - _rhs(std::make_shared>(std::forward(rhs))) { - } - - bool match(const cq::Target &target, const std::string_view &message, Session &session) const override { - return _lhs->match(target, message, session) && _rhs->match(target, message, session); - } - - protected: - std::shared_ptr _lhs; - std::shared_ptr _rhs; - }; - - class _OrMatcher : public MatcherBase { - public: - template >> - _OrMatcher(TL &&lhs, TR &&rhs) - : _lhs(std::make_shared>(std::forward(lhs))), - _rhs(std::make_shared>(std::forward(rhs))) { - } - - bool match(const cq::MessageEvent &event, Session &session) const override { - return _match(event, session); - } - - bool match(const cq::NoticeEvent &event, Session &session) const override { - return _match(event, session); - } - - bool match(const cq::RequestEvent &event, Session &session) const override { - return _match(event, session); - } - - bool match(const cq::UserEvent &event, Session &session) const override { - return _match(event, session); - } - - protected: - std::shared_ptr _lhs; - std::shared_ptr _rhs; - - template - bool _match(const E &event, Session &session) const { - return _lhs->match(event, session) || _rhs->match(event, session); - } - }; - - class _OrMessageMatcher : public MessageMatcher { - public: - template >> - _OrMessageMatcher(TL &&lhs, TR &&rhs) - : _lhs(std::make_shared>(std::forward(lhs))), - _rhs(std::make_shared>(std::forward(rhs))) { - } - - bool match(const cq::Target &target, const std::string_view &message, Session &session) const override { - return _lhs->match(target, message, session) || _rhs->match(target, message, session); - } - - protected: - std::shared_ptr _lhs; - std::shared_ptr _rhs; - }; - template >> - inline auto operator!(T &&matcher) { - if constexpr (is_message_matcher_v) { - return _NotMessageMatcher(std::forward(matcher)); - } else { - return _NotMatcher(std::forward(matcher)); - } + template + constexpr auto all(Matchers &&... matchers) { + return [=](auto &&... args) { return (matchers(args...) && ...); }; } - template >> - inline auto operator&&(TL &&lhs, TR &&rhs) { - if constexpr (is_message_matcher_v && is_message_matcher_v) { - return _AndMessageMatcher(std::forward(lhs), std::forward(rhs)); - } else { - return _AndMatcher(std::forward(lhs), std::forward(rhs)); - } - } + template + constexpr auto type = [](const auto &event, Session &session) { return typeid(event) == typeid(E); }; - template >> - inline auto operator||(TL &&lhs, TR &&rhs) { - if constexpr (is_message_matcher_v && is_message_matcher_v) { - return _OrMessageMatcher(std::forward(lhs), std::forward(rhs)); - } else { - return _OrMatcher(std::forward(lhs), std::forward(rhs)); - } + constexpr auto unblocked() { + return [](const cq::UserEvent &event, Session &session) { return !event.blocked(); }; } - class all : public MatcherBase { + class startswith { public: - template - explicit all(Matchers &&... matchers) - : _matchers({std::make_shared>(std::forward(matchers))...}) { - } - - bool match(const cq::MessageEvent &event, Session &session) const override { - return _match(event, session); - } - - bool match(const cq::NoticeEvent &event, Session &session) const override { - return _match(event, session); + explicit constexpr startswith(std::string_view prefix) : _prefix(prefix) { } - bool match(const cq::RequestEvent &event, Session &session) const override { - return _match(event, session); + constexpr bool operator()(const cq::Target &target, const std::string_view &message, Session &session) const { + return string::startswith(message, _prefix); } - bool match(const cq::UserEvent &event, Session &session) const override { - return _match(event, session); + bool operator()(const cq::MessageEvent &event, Session &session) const { + return (*this)(event.target, event.message, session); } protected: - std::vector> _matchers; - - template - bool _match(const E &event, Session &session) const { - return std::all_of(_matchers.cbegin(), _matchers.cend(), [&](const auto &matcher) { - return matcher->match(event, session); - }); - } - }; - - template - struct _type { - class matcher_t : public MatcherBase { - public: - bool match(const cq::UserEvent &event, Session &session) const override { - return typeid(event) == typeid(E); - } - }; - - static constexpr matcher_t matcher{}; + std::string_view _prefix; }; - template - constexpr auto type = _type::matcher; - - class unblocked : public MatcherBase { + class endswith { public: - bool match(const cq::UserEvent &event, Session &session) const override { - return !event.blocked(); + explicit constexpr endswith(std::string_view suffix) : _suffix(suffix) { } - }; - class startswith : public MessageMatcher { - public: - explicit startswith(std::string prefix) : _prefix(std::move(prefix)) { + constexpr bool operator()(const cq::Target &target, const std::string_view &message, Session &session) const { + return string::endswith(message, _suffix); } - bool match(const cq::Target &target, const std::string_view &message, Session &session) const override { - return string::startswith(message, _prefix); + bool operator()(const cq::MessageEvent &event, Session &session) const { + return (*this)(event.target, event.message, session); } protected: - std::string _prefix; + std::string_view _suffix; }; - class endswith : public MessageMatcher { + class contains { public: - explicit endswith(std::string suffix) : _suffix(std::move(suffix)) { - } - - bool match(const cq::Target &target, const std::string_view &message, Session &session) const override { - return string::endswith(message, _suffix); + explicit constexpr contains(std::string_view sub) : _sub(sub) { } - protected: - std::string _suffix; - }; - - class contains : public MessageMatcher { - public: - explicit contains(std::string sub) : _sub(std::move(sub)) { + constexpr bool operator()(const cq::Target &target, const std::string_view &message, Session &session) const { + return string::contains(message, _sub); } - bool match(const cq::Target &target, const std::string_view &message, Session &session) const override { - return string::contains(message, _sub); + bool operator()(const cq::MessageEvent &event, Session &session) const { + return (*this)(event.target, event.message, session); } protected: - std::string _sub; + std::string_view _sub; }; - class command : public MessageMatcher { + class command { public: static constexpr auto STARTER = "_cond__command__starter"; static constexpr auto NAME = "_cond__command__name"; @@ -332,9 +113,8 @@ namespace dolores { : _names(names), _starters(std::move(starters)) { } - bool match(const cq::Target &target, const std::string_view &message, Session &session) const override { + bool operator()(const cq::Target &target, const std::string_view &message, Session &session) const { static const std::vector default_starters = {"/", "!", ".", "!", "。"}; - const auto message_v = string::string_view_from( std::find_if_not(message.cbegin(), message.cend(), cq::utils::isspace_s), message.cend()); @@ -369,22 +149,24 @@ namespace dolores { return res; } + bool operator()(const cq::MessageEvent &event, Session &session) const { + return (*this)(event.target, event.message, session); + } + protected: std::vector _names; std::vector _starters; }; - class to_me : public MessageMatcher { + template + class to_me { public: - to_me() = default; - - template >> - explicit to_me(T &&matcher) : _sub_matcher(std::make_shared>(std::forward(matcher))) { + explicit to_me(T &&matcher) : _sub_matcher(matcher) { } - bool match(const cq::Target &target, const std::string_view &message, Session &session) const override { + bool operator()(const cq::Target &target, const std::string_view &message, Session &session) const { if (target.is_private()) { - return _sub_matcher ? _sub_matcher->match(target, message, session) : true; + return _sub_matcher(target, message, session); } using cq::message::MessageSegment; @@ -392,8 +174,6 @@ namespace dolores { const auto at_me_off = message.find(at_me_seg); if (at_me_off == std::string_view::npos) { return false; - } else if (!_sub_matcher) { - return true; } // assert: _sub_matcher is not null @@ -416,14 +196,46 @@ namespace dolores { // @me is in the middle of message cut_message_v = message; } - return _sub_matcher->match(target, cut_message_v, session); + return _sub_matcher(target, cut_message_v, session); + }; + + bool operator()(const cq::MessageEvent &event, Session &session) const { + return (*this)(event.target, event.message, session); } private: - std::shared_ptr _sub_matcher; + T _sub_matcher; }; - class user : public MatcherBase { + template <> + class to_me { + public: + constexpr to_me() = default; + + bool operator()(const cq::Target &target, const std::string_view &message, Session &session) const { + if (target.is_private()) { + return true; + } + + using cq::message::MessageSegment; + const auto at_me_seg = cq::to_string(MessageSegment::at(watashi::user_id())); + const auto at_me_off = message.find(at_me_seg); + if (at_me_off == std::string_view::npos) { + return false; + } + + return true; + }; + + bool operator()(const cq::MessageEvent &event, Session &session) const { + return (*this)(event.target, event.message, session); + } + }; + to_me()->to_me; + template + to_me(T)->to_me; + + class user { public: user() = default; @@ -436,7 +248,7 @@ namespace dolores { return u; } - bool match(const cq::UserEvent &event, Session &session) const override { + bool operator()(const cq::UserEvent &event, Session &session) const { if (!_include_users.empty()) { return std::find(_include_users.cbegin(), _include_users.cend(), event.user_id) != _include_users.cend(); @@ -463,13 +275,13 @@ namespace dolores { return d; } - bool match(const cq::UserEvent &event, Session &session) const override { + bool operator()(const cq::UserEvent &event, Session &session) const { if (!event.target.is_private()) return false; - return user::match(event, session); + return user::operator()(event, session); } }; - class group : public MatcherBase { + class group { public: group() = default; @@ -482,7 +294,7 @@ namespace dolores { return g; } - bool match(const cq::UserEvent &event, Session &session) const override { + bool operator()(const cq::UserEvent &event, Session &session) const { if (!event.target.is_group()) return false; const auto group_id = event.target.group_id.value_or(0); @@ -502,30 +314,37 @@ namespace dolores { std::vector _exclude_groups; }; - class discuss : public MatcherBase { + class discuss { public: - bool match(const cq::UserEvent &event, Session &session) const override { + bool operator()(const cq::UserEvent &event, Session &session) const { return event.target.is_discuss(); } }; - class group_roles : public MatcherBase { + class group_roles { public: - explicit group_roles(std::vector roles) : _roles(std::move(roles)) { - } + static constexpr std::size_t MAX_ROLES = 3; + + //template + //constexpr group_roles(std::initializer_list il, std::index_sequence) : _roles(((1ull << static_cast(*(il.begin() + I))) || ...)) {} + + //constexpr group_roles(std::initializer_list il) : group_roles(il, std::make_index_sequence()) {} + + template && ...)>> + constexpr group_roles(Args...args) : _roles(((1ull << static_cast(args)) || ...)) { } - bool match(const cq::UserEvent &event, Session &session) const override { + bool operator()(const cq::UserEvent &event, Session &session) const { if (!event.target.is_group()) return true; // ignore non-group event const auto group_id = event.target.group_id.value_or(0); try { const auto mi = cq::get_group_member_info(group_id, event.user_id); - return std::find(_roles.cbegin(), _roles.cend(), mi.role) != _roles.cend(); + return _roles.test(static_cast(mi.role)); } catch (cq::ApiError &) { // try again with cache disabled try { const auto mi = cq::get_group_member_info(group_id, event.user_id, true); - return std::find(_roles.cbegin(), _roles.cend(), mi.role) != _roles.cend(); + return _roles.test(static_cast(mi.role)); } catch (cq::ApiError &) { return false; } @@ -533,18 +352,18 @@ namespace dolores { } protected: - std::vector _roles; + std::bitset _roles; }; class admin : public group_roles { public: - admin() : group_roles({cq::GroupRole::ADMIN, cq::GroupRole::OWNER}) { + constexpr admin() : group_roles({cq::GroupRole::ADMIN, cq::GroupRole::OWNER}) { } }; class owner : public group_roles { public: - owner() : group_roles({cq::GroupRole::OWNER}) { + constexpr owner() : group_roles({cq::GroupRole::OWNER}) { } }; } // namespace matchers diff --git a/include/dolores/string.hpp b/include/dolores/string.hpp index e6ec22c..43b9dd7 100644 --- a/include/dolores/string.hpp +++ b/include/dolores/string.hpp @@ -1,25 +1,28 @@ #pragma once #include +#include namespace dolores::string { - inline bool startswith(const std::string_view &sv, const std::string_view &prefix) { + constexpr bool startswith(std::string_view sv, std::string_view prefix) { return sv.substr(0, prefix.length()) == prefix; } - inline bool endswith(const std::string_view &sv, const std::string_view &suffix) { + constexpr bool endswith(std::string_view sv, std::string_view suffix) { if (sv.length() < suffix.length()) return false; return sv.substr(sv.length() - suffix.length()) == suffix; } - inline bool contains(const std::string_view &sv, const std::string_view &sub) { + constexpr bool contains(std::string_view sv, std::string_view sub) { return sv.find(sub) != std::string_view ::npos; } - template - inline std::string_view string_view_from(It first, End last) { - const auto size = last - first; - if (size <= 0) return std::string_view(""); - return std::string_view(&*first, size); + constexpr std::string_view string_view_from(std::string_view::const_iterator first, std::string_view::const_iterator last) { + return std::string_view(&*first, std::distance(first, last)); } + + inline std::string_view string_view_from(std::string::const_iterator first, std::string::const_iterator last) { + return std::string_view(&*first, std::distance(first, last)); + } + } // namespace dolores::string diff --git a/include/dolores/traits.hpp b/include/dolores/traits.hpp index 1400f5a..4c9ee79 100644 --- a/include/dolores/traits.hpp +++ b/include/dolores/traits.hpp @@ -9,15 +9,37 @@ namespace dolores { static constexpr auto is_derived_from_user_event_v = std::is_base_of_v> && !std::is_same_v>; - struct MatcherBase; + namespace matchers { +#if __cpp_concepts + template + concept MatcherBase = requires(T matcher, const cq::MessageEvent &event, Session &session) { + { matcher(event, session) } + ->std::boolean; + }; - template - static constexpr auto is_matcher_v = - std::is_base_of_v>&& std::is_base_of_v>; + template + concept MessageMatcher = MatcherBase &&requires(T matcher, const cq::Target &target, + std::string_view message, Session &session) { + { matcher(target, message, session) } + ->std::boolean; + }; +#endif + inline namespace { + template + auto is_in_matchers_namespace(T &&) +#if __cpp_concepts + requires MatcherBase +#else + -> std::enable_if_t, std::true_type> +#endif + { + return std::true_type(); + } + } // namespace + } - struct MessageMatcher; + std::false_type is_in_matchers_namespace(...); - template - static constexpr auto is_message_matcher_v = - std::is_base_of_v>&& std::is_base_of_v>; + template + static constexpr auto is_matcher_v = decltype(is_in_matchers_namespace(std::declval()))::value; } // namespace dolores diff --git a/tests/test_dolores_matcher.cpp b/tests/test_dolores_matcher.cpp index ea7ed57..c78a00f 100644 --- a/tests/test_dolores_matcher.cpp +++ b/tests/test_dolores_matcher.cpp @@ -10,9 +10,16 @@ std::pair construct_pm() { return {cq::PrivateMessageEvent(1, 1, "hello, world", 0, cq::PrivateMessageEvent::SubType::FRIEND), Session()}; } +template +struct MatcherWrapper +{ + template bool match(Args &&...args) const { return x(std::forward(args)...); } + T x; +}; + template -std::shared_ptr to_matcher(Cond &&c) { - return std::make_shared>(std::forward(c)); +auto to_matcher(Cond &&c) { + return std::make_shared>(MatcherWrapper{std::forward(c)}); } TEST_CASE("matchers::operator!", "[matcher]") {