diff --git a/include/rosa/core/Invoker.hpp b/include/rosa/core/Invoker.hpp index 55b5bfb..74bb11b 100644 --- a/include/rosa/core/Invoker.hpp +++ b/include/rosa/core/Invoker.hpp @@ -1,265 +1,265 @@ /***************************************************************************//** * * \file rosa/core/Invoker.hpp * * \author David Juhasz (david.juhasz@tuwien.ac.at) * * \date 2017 * * \brief Facilities for providing actual arguments for functions as * `rosa::Message`objects. * ******************************************************************************/ #ifndef ROSA_CORE_INVOKER_HPP #define ROSA_CORE_INVOKER_HPP #include "rosa/core/MessageMatcher.hpp" #include "rosa/support/log.h" #include #include namespace rosa { /// Wraps a function and provides a simple interface to invoke the stored /// function by passing actual arguments as a `rosa::Message` object. /// /// \note A `rosa::Invoker` instance is supposed to be owned by a /// `rosa::MessageHandler` instance, and not being used directly from user code. class Invoker { protected: /// Creates an instance. /// /// \note Protected constructor restricts instantiation to derived classes. Invoker(void) noexcept; public: /// Destroys `this` object. virtual ~Invoker(void); /// Possible results of an invocation. enum class Result { NoMatch, ///< The wrapped function could not be invoked Invoked ///< The wrapped function has been invoked }; /// Type alias for a smart-pointer for `rosa::Invoker`. using invoker_t = std::unique_ptr; /// Type alias for `rosa::Invoker::Result`. using result_t = Result; /// Tells if a `rosa::Message` object can be used to invoke the function /// wrapped in `this` object. /// /// \param Msg `rosa::Message` to check /// /// \return whether `Msg` can be used to invoke the wrapped function virtual bool match(const Message &Msg) const noexcept = 0; /// Tries to invoke the wrapped function with a `rosa::Message` object. /// /// The wrapped function is invoked if the actual `rosa::Message` object can /// be used to invoke it. /// /// \param Msg `rosa::Message` to try to invoke the wrapped function with /// /// \return whether the wrapped function could be invoked with `Msg` virtual result_t operator()(const Message &Msg) const noexcept = 0; /// Instantiates an implementation of `rosa::Invoker` with the given function. /// /// \note As there is no empty `rosa::Message`, no `rosa::Invoker` wraps a /// function without any argument. /// /// \tparam T type of the first mandatory argument /// \tparam Ts types of any further arguments /// /// \param F function to wrap /// /// \return new `invoker_t` object created from the given function template static invoker_t wrap(std::function &&F) noexcept; /// Convenience template alias for casting callable stuff to function objects /// for wrapping. /// /// \tparam Ts types of arguments /// /// \todo Should make it possible to avoid using an explicit conversion for /// the arguments of wrap. template using F = std::function; /// Convenience template for preparing non-static member functions into /// function objects for wrapping. /// /// \tparam C type whose non-static member the function is /// \tparam Ts types of arguments /// /// \see THISMEMBER template static inline F M(C *O, void (C::*Fun)(Ts...) noexcept) noexcept; }; /// Convenience preprocessor macro for the typical use of `rosa::Invoker::M`. It /// can be used inside a class to turn a non-static member function into a /// function object capturing this pointer, so using the actual object when /// handling a `rosa::Message`. /// -/// \param `FUN` the non-static member function to wrap +/// \param FUN the non-static member function to wrap /// /// \note Inside the class `MyClass`, use\code /// THISMEMBER(fun) /// \endcode instead of\code /// Invoker::M(this, &MyClass::fun) /// \endcode #define THISMEMBER(FUN) \ Invoker::M(this, &std::decay::type::FUN) /// Nested namespace with implementation of `rosa::Invoker` and helper /// templates, consider it private. namespace { /// \defgroup Seq /// ///@{ /// Template with an empty struct to store a sequence of numbers in compile time /// as template arguments. template struct Seq {}; /// Sequence generator, the general case when counting down by extending the /// sequence. template struct GenSeq : GenSeq {}; /// Sequence generator, the terminal case when storing the generated sequence /// into `rosa::Seq`. template struct GenSeq<0, S...> { using Type = Seq; }; ///@} /// \defgroup InvokerImpl /// /// Implements the `rosa::Invoker` interface for functions with different /// signatures. /// ///@{ /// Declaration of `rosa::InvokerImpl` implementing `rosa::Invoker`. /// /// \tparam Fun function to wrap template class InvokerImpl; /// Implementation of `rosa::InvokerImpl` for `std::function`. /// /// \tparam T type of the first mandatory argument /// \tparam Ts types of further arguments /// /// \note As there is no empty `rosa::Message`, no `rosa::Invoker` wraps a /// function without any argument, i.e., no std::function. template class InvokerImpl> final : public Invoker { /// Type alias for the stored function. using function_t = std::function; /// Type alias for correctly typed argument-tuples as obtained from /// `rosa::Message`. using args_t = std::tuple; /// Alias for `rosa::MessageMatcher` for the arguments of the stored function. using Matcher = MsgMatcher; /// The wrapped function. const function_t F; /// Invokes `F` by unpacking arguments from a `std::tuple` with the help of /// the actual template arguments. /// /// \tparam S sequence of numbers indexing `std::tuple` for arguments /// /// \param Args arguments to invoke `F` with /// /// \pre the length of `S` and size of `Args` are matching:\code /// sizeof...(S) == std::tuple_size::value /// \endcode template inline void invokeFunction(Seq, const args_t &Args) const noexcept; public: /// Creates an instance. /// /// \param F function to wrap /// /// \pre `F` is valid:\code /// bool(F) /// \endcode InvokerImpl(function_t &&F) noexcept : F(F) { ASSERT(bool(F)); // Sanity check. } /// Destroys `this` object. ~InvokerImpl(void) = default; /// Tells if a `rosa::Message` object can be used to invoke the function /// wrapped in `this` object. /// /// \param Msg `rosa::Message` to check /// /// \return whether `Msg` can be used to invoke the wrapped function bool match(const Message &Msg) const noexcept override { return Matcher::doesStronglyMatch(Msg); }; /// Tries to invoke the wrapped function with a `rosa::Message` object. /// /// The wrapped function is invoked if the actual `rosa::Message` object can /// be used to invoke it. /// /// \param Msg `rosa::Message` to try to invoke the wrapped function with /// /// \return whether the wrapped function could be invoked with `Msg` result_t operator()(const Message &Msg) const noexcept override { if (match(Msg)) { LOG_TRACE("Invoking with matching arguments"); invokeFunction(typename GenSeq::Type(), Matcher::extractedValues(Msg)); return result_t::Invoked; } else { LOG_TRACE("Tried to invoke with non-matching arguments"); return result_t::NoMatch; } } }; template template void InvokerImpl>::invokeFunction( Seq, const args_t &Args) const noexcept { ASSERT(sizeof...(S) == std::tuple_size::value); // Sanity check. F(std::get(Args)...); } ///@} } // End namespace template Invoker::invoker_t Invoker::wrap(std::function &&F) noexcept { return std::unique_ptr( new InvokerImpl>(std::move(F))); } template Invoker::F Invoker::M(C *O, void (C::*Fun)(Ts...) noexcept) noexcept { return [ O, Fun ](Ts... Vs) noexcept->void { (O->*Fun)(Vs...); }; } } // End namespace rosa #endif // ROSA_CORE_INVOKER_HPP diff --git a/include/rosa/support/log.h b/include/rosa/support/log.h index 9e3763c..be7363c 100644 --- a/include/rosa/support/log.h +++ b/include/rosa/support/log.h @@ -1,257 +1,257 @@ /***************************************************************************//** * * \file rosa/support/log.h * * \author David Juhasz (david.juhasz@tuwien.ac.at) * * \date 2017 * * \brief Facility for logging. * * \note One call for the various logging macros is supposed to be used * for registering one log entry. That goes natural with the non-stream * implementations, which accept one string as argument. A more flexible way * for printing log entries, for example for colorizing text, is to use macros * providing a log stream. It is important to note, however, that the stream * obtained from one macro evaluation fits for printing one log entry, without * nested/overlapping log entry emissions and the entry being closed with a * newline. Should this simple recommendation not being followed, the result * becomes hard to read due to missing line breaks and overlapping entries. * * \todo Thread-safety is another issue, which need to be addressed for proper * logging. * ******************************************************************************/ #ifndef ROSA_SUPPORT_LOG_H #define ROSA_SUPPORT_LOG_H #include "rosa/config/config.h" #include "rosa/support/terminal_colors.h" #include #include /* **************************************************************************** * Log Levels * * ****************************************************************************/ namespace rosa { /// Type-safe definition of log levels, use this in code. /// \note Keep values in sync with the corresponding preprocessor definitions. enum class LogLevel { Error, ///< Log errors only Warning, ///< Like `rosa::LogLevel::Error` and also log warnings Info, ///< Like `rosa::LogLevel::Warning` and also log general infos Debug, ///< Like `rosa::LogLevel::Info` and also log debug infos Trace, ///< Like `rosa::LogLevel::Debug` and also log trace infos NumLogLevels ///< Number of log levels }; /// Converts a `rosa::LogLevel` to its textual representation. /// /// \param logLevel the `LogLevel` to convert /// /// \return `string` representing `logLevel` /// /// \pre `logLevel` is valid:\code /// logLevel != LogLevel::NumLogLevels /// \endcode std::string logLevelToString(const LogLevel logLevel); /// Prints colorized tag for the given `rosa::LogLevel`. /// /// \param os `ostream` to print to /// \param logLevel the `LogLevel` to print tag for /// /// \return `os` after printing a tag for `logLevel` /// /// \pre `logLevel` is valid:\code /// logLevel != LogLevel::NumLogLevels /// \endcode std::ostream &operator<<(std::ostream &os, const LogLevel logLevel); } // End namespace rosa /// \name Valid log level constants /// \note Only for preprocessor definitions in this file. /// \note Keep the defintions in sync with the values of `rosa::LogLevel`. ///@{ #define ROSA_LOG_LEVEL_ERROR \ 0 ///< Value corresponding to `rosa::LogLevel::Error` #define ROSA_LOG_LEVEL_WARNING \ - 1 ///< Value corresponding to `rosa::LogLevel::Warning. + 1 ///< Value corresponding to `rosa::LogLevel::Warning` #define ROSA_LOG_LEVEL_INFO \ 2 ///< Value corresponding to `rosa::LogLevel::Info` #define ROSA_LOG_LEVEL_DEBUG \ 3 ///< Value corresponding to `rosa::LogLevel::Debug` #define ROSA_LOG_LEVEL_TRACE \ 4 ///< Value corresponding to `rosa::LogLevel::Trace` ///@} /* **************************************************************************** * Logger Implementation * * ****************************************************************************/ /// Stream to print logs to /// /// \todo Make it configurable, e.g. printing into a file. #define ROSA_LOG_OSTREAM std::clog /// Prints a log message to `ROSA_LOG_OSTREAM`. /// /// \param level `LogLevel` of the log entry /// \param output message to print #define ROSA_LOG_IMPL(level, output) \ do { \ ROSA_LOG_OSTREAM << (level) << " " << __func__ << "@" << __FILENAME__ \ << ":" << __LINE__ << ": " << (output) << std::endl; \ } while (false) /// Returns a stream to print a log message to. /// /// \param level `LogLevel`of the log entry that is about to be printed #define ROSA_LOG_STREAM_IMPL(level) \ ([](void) -> std::ostream & { \ return ROSA_LOG_OSTREAM << (level) << " " << __func__ << "@" \ << __FILENAME__ << ":" << __LINE__ << ": "; \ }()) namespace rosa { /// Dummy `std::ostream` printing to nowhere. extern std::ostream LogSink; } // End namespace rosa /// An output stream ignoring all its input. #define ROSA_LOG_STREAM_IGNORE rosa::LogSink /* **************************************************************************** * Logging Interface * * ****************************************************************************/ /// \name Logging interface /// /// Preprocesser macros for convenience. ///@{ /// \def LOG_ERROR_STREAM /// \brief Returns a stream to print a log entry of level /// `rosa::LogLevel::Error`. /// \note Takes effect only if RoSA is built with a log level not smaller than /// `rosa::LogLevel::Error`. /// \def LOG_ERROR(output) /// \brief Prints a log entry of level `rosa::LogLevel::Error`. /// \param output the message to print /// \note Takes effect only if RoSA is built with a log level not smaller than /// `rosa::LogLevel::Error`. /// \def LOG_WARNING_STREAM /// \brief Returns a stream to print a log entry of level /// `rosa::LogLevel::Warning`. /// \note Takes effect only if RoSA is built with a log level not smaller than /// `rosa::LogLevel::Warning`. /// \def LOG_WARNING(output) /// \brief Prints a log entry of level `rosa::LogLevel::Warning`. /// \param output the message to print /// \note Takes effect only if RoSA is built with a log level not smaller than /// `rosa::LogLevel::Warning`. /// \def LOG_INFO_STREAM /// \brief Returns a stream to print a log entry of level /// `rosa::LogLevel::Info`. /// \note Takes effect only if RoSA is built with a log level not smaller than /// `rosa::LogLevel::Info`. /// \def LOG_INFO(output) /// \brief Prints a log entry of level `rosa::LogLevel::Info`. /// \param output the message to print /// \note Takes effect only if RoSA is built with a log level not smaller than /// `rosa::LogLevel::Info`. /// \def LOG_DEBUG_STREAM /// \brief Returns a stream to print a log entry of level /// `rosa::LogLevel::Debug`. /// \note Takes effect only if RoSA is built with a log level not smaller than /// `rosa::LogLevel::Debug`. /// \def LOG_DEBUG(output) /// \brief Prints a log entry of level `rosa::LogLevel::Debug`. /// \param output the message to print /// \note Takes effect only if RoSA is built with a log level not smaller than /// `rosa::LogLevel::Debug`. /// \def LOG_TRACE_STREAM /// \brief Returns a stream to print a log entry of level /// `rosa::LogLevel::Trace`. /// \note Takes effect only if RoSA is built with a log level not smaller than /// `rosa::LogLevel::Trace`. /// \def LOG_TRACE(output) /// \brief Prints a log entry of level `rosa::LogLevel::Trace`. /// \param output the message to print /// \note Takes effect only if RoSA is built with a log level not smaller than /// `rosa::LogLevel::Trace`. ///@} // Define logging macros if logging is enabled. #ifdef ROSA_LOG_LEVEL #define LOG_ERROR_STREAM ROSA_LOG_STREAM_IMPL(rosa::LogLevel::Error) #define LOG_ERROR(output) ROSA_LOG_IMPL(rosa::LogLevel::Error, output) #if ROSA_LOG_LEVEL >= ROSA_LOG_LEVEL_WARNING #define LOG_WARNING_STREAM ROSA_LOG_STREAM_IMPL(rosa::LogLevel::Warning) #define LOG_WARNING(output) ROSA_LOG_IMPL(rosa::LogLevel::Warning, output) #endif #if ROSA_LOG_LEVEL >= ROSA_LOG_LEVEL_INFO #define LOG_INFO_STREAM ROSA_LOG_STREAM_IMPL(rosa::LogLevel::Info) #define LOG_INFO(output) ROSA_LOG_IMPL(rosa::LogLevel::Info, output) #endif #if ROSA_LOG_LEVEL >= ROSA_LOG_LEVEL_DEBUG #define LOG_DEBUG_STREAM ROSA_LOG_STREAM_IMPL(rosa::LogLevel::Debug) #define LOG_DEBUG(output) ROSA_LOG_IMPL(rosa::LogLevel::Debug, output) #endif #if ROSA_LOG_LEVEL >= ROSA_LOG_LEVEL_TRACE #define LOG_TRACE_STREAM ROSA_LOG_STREAM_IMPL(rosa::LogLevel::Trace) #define LOG_TRACE(output) ROSA_LOG_IMPL(rosa::LogLevel::Trace, output) #endif #endif // defined ROSA_LOG_LEVEL // Define all disabled logging features as void. #ifndef LOG_ERROR #define LOG_ERROR_STREAM ROSA_LOG_STREAM_IGNORE #define LOG_ERROR(output) ROSA_IGNORE_UNUSED(output) #endif #ifndef LOG_WARNING #define LOG_WARNING_STREAM ROSA_LOG_STREAM_IGNORE #define LOG_WARNING(output) ROSA_IGNORE_UNUSED(output) #endif #ifndef LOG_INFO #define LOG_INFO_STREAM ROSA_LOG_STREAM_IGNORE #define LOG_INFO(output) ROSA_IGNORE_UNUSED(output) #endif #ifndef LOG_DEBUG #define LOG_DEBUG_STREAM ROSA_LOG_STREAM_IGNORE #define LOG_DEBUG(output) ROSA_IGNORE_UNUSED(output) #endif #ifndef LOG_TRACE #define LOG_TRACE_STREAM ROSA_LOG_STREAM_IGNORE #define LOG_TRACE(output) ROSA_IGNORE_UNUSED(output) #endif #endif // ROSA_SUPPORT_LOG_H