diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt index eb5ec40..c5f89df 100644 --- a/examples/CMakeLists.txt +++ b/examples/CMakeLists.txt @@ -1,4 +1,5 @@ # Add the different subdirectories add_subdirectory(basic-system) add_subdirectory(type-facilities) +add_subdirectory(messaging) diff --git a/examples/messaging/CMakeLists.txt b/examples/messaging/CMakeLists.txt new file mode 100644 index 0000000..16d6471 --- /dev/null +++ b/examples/messaging/CMakeLists.txt @@ -0,0 +1,4 @@ +add_executable(messaging messaging.cpp) +ROSA_add_library_dependencies(messaging ROSAConfig) +ROSA_add_library_dependencies(messaging ROSACore) + diff --git a/examples/messaging/messaging.cpp b/examples/messaging/messaging.cpp new file mode 100644 index 0000000..0ee50f1 --- /dev/null +++ b/examples/messaging/messaging.cpp @@ -0,0 +1,134 @@ +/******************************************************************************* + * + * File: messaging.cpp + * + * Contents: An example showcasing features related to Messages. + * + * Copyright 2017 + * + * Author: David Juhasz (david.juhasz@tuwien.ac.at) + * + ******************************************************************************/ + +#include "rosa/config/version.h" +#include "rosa/core/Invoker.hpp" +#include "rosa/support/log.h" +#include "rosa/support/terminal_colors.h" +#include + +using namespace rosa; +using namespace rosa::terminal; + +// A simple implementation of the Message interface with two hardcoded types. +class MyMessage : public Message { + const uint8_t U8; // First value + const uint16_t U16; // Second value +public: + MyMessage(const uint8_t U8, const uint16_t U16) + : Message(U8, U16), U8(U8), U16(U16) {} + + const void *getPointerTo(const size_t Pos) const noexcept override { + switch (Pos) { + default: + ROSA_CRITICAL("wrong position"); + case 0: + return &U8; + case 1: + return &U16; + } + } +}; + +class MyAtomMessage : public Message { + const AtomValue A; // First value +public: + template + MyAtomMessage(AtomConstant C) : Message(C), A(V) {} + + const void *getPointerTo(const size_t Pos) const noexcept override { + switch (Pos) { + default: + ROSA_CRITICAL("wrong position"); + case 0: + return &A; + } + } +}; + +int main(void) { + LOG_INFO_STREAM << library_string() << " -- " << Color::Red + << "messaging" << Color::Default << std::endl; + + auto &Log = LOG_TRACE_STREAM << std::endl; + + MyMessage Msg(1, 2); + Log << "Checking on 'MyMessage(1, 2)' that is of 'Message with " + "TypeList<>':" + << std::endl + << " Size: " << Msg.size() << std::endl + << " Pos 0 is uint8_t: " << Msg.isTypeAt(0) << std::endl + << " Pos 1 is uint16_t: " << Msg.isTypeAt(1) << std::endl + << " Pos 2 is uint32_t: " << Msg.isTypeAt(1) << std::endl + << " Value at pos 0: " << PRINTABLE(uint8_t)(Msg.getValueAt(0)) + << std::endl + << " Value at pos 1: " << Msg.getValueAt(1) << std::endl + << std::endl; + + using MyMatcher = MsgMatcher; + Log << "Matching against 'TypeList':" << std::endl + << " matching: " << MyMatcher::doesStronglyMatch(Msg) << std::endl; + auto Vs = MyMatcher::extractedValues(Msg); + Log << " value: '(" << PRINTABLE(uint8_t)(std::get<0>(Vs)) << ", " + << std::get<1>(Vs) << ")'" << std::endl + << std::endl; + + using MyWrongMatcher = MsgMatcher; + Log << "Matching against 'TypeList':" << std::endl + << " matching: " << MyWrongMatcher::doesStronglyMatch(Msg) << std::endl + << std::endl; + + using MyAtom = AtomConstant; + const MyAtom &A = MyAtom::Value; + using MyNAtom = AtomConstant; + MyAtomMessage AMsg(A); + Log << "Checking on 'MyAtomMessage(AtomConstant::Value)' " + "that is of 'Message with TypeList>':" + << std::endl + << " Size: " << AMsg.size() << std::endl + << " Pos 0 is 'AtomValue': " << AMsg.isTypeAt(0) << std::endl + << " Pos 0 is 'AtomConstant': " + << AMsg.isTypeAt(0) << std::endl + << std::endl; + + using MyAtomMatcher = MsgMatcher; + Log << "Matching against 'TypeList>':" + << std::endl + << " matching: " << MyAtomMatcher::doesStronglyMatch(AMsg) << std::endl + << " value: '(" + << to_string(std::get<0>(MyAtomMatcher::extractedValues(AMsg))) << ")'" + << std::endl + << std::endl; + + using MyWrongAtomMatcher = MsgMatcher; + Log << "Matching against 'TypeList>':" + << std::endl + << " matching: " << MyWrongAtomMatcher::doesStronglyMatch(AMsg) + << std::endl + << std::endl; + + // FIXME: Is this a compiler bug, + // cannot use turnIntoInvoker([&](MyAtom)... ??? + auto Inv = turnIntoInvoker; + auto I = Inv([&Log](MyAtom) noexcept->void { + Log << "** Lambda-function called via Invoker." << std::endl; + }); + Log << "Invoking a function of signature 'void(MyAtom) noexcept':" + << std::endl + << " with 'Message with TypeList'..." << std::endl; + I(Msg); + Log << " with 'Message with TypeList'..." << std::endl; + I(AMsg); + + return 0; +} + diff --git a/include/rosa/core/Invoker.hpp b/include/rosa/core/Invoker.hpp new file mode 100644 index 0000000..6998f0d --- /dev/null +++ b/include/rosa/core/Invoker.hpp @@ -0,0 +1,136 @@ +/******************************************************************************* + * + * File: Invoker.hpp + * + * Contents: Implementation of Invoker. + * + * Copyright 2017 + * + * Author: David Juhasz (david.juhasz@tuwien.ac.at) + * + ******************************************************************************/ + +#ifndef ROSA_CORE_INVOKER_HPP +#define ROSA_CORE_INVOKER_HPP + +#include "rosa/core/MessageMatcher.hpp" +#include "rosa/support/log.h" +#include + +namespace rosa { + +// Wraps a function and provides a simple interface to invoke the stored +// function by passing actual arguments as a Message. +// NOTE: Invoker instances are supposed to be owned by Message handlers, and +// not being used directly from user code. +// NOTE: As there is no empty Message, no Invoker wraps a function without an +// argument. +template class Invoker; + +// Convenience function for instantiating Invoker and utilizing template +// argument deduction. +// FIXME: C++17 supports class template deduction in some contexts. May that be +// usable to directly instantiate Invoker? +// FIXME: Alternatively, use the Callable concept of C++17? +template +Invoker> +turnIntoInvoker(std::function &&F) noexcept { + return Invoker>(std::move(F)); +} + +// Nested namespace with helper templates, consider it private. +namespace { + +// Empty struct just 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 Seq. +template struct GenSeq<0, S...> { using Type = Seq; }; + +}; // End namespace + +// Specialization of Invoker template for std::function. +// NOTE: No std::function. +template +class Invoker> final { + + // Type alias for the stored function. + using function_t = std::function; + + // Type alias for correctly typed argument-tuples as obtained from Messages. + using args_t = std::tuple; + + // The wrapped function. + const function_t F; + + // Helper function invoking F by unpacking Args with the help of actual + // template arguments. + // PRE: sizeof...(S) == std::tuple_size::value + template + inline void invokeFunction(Seq, const args_t &Args) const noexcept; + +public: + // Ctor. + Invoker(function_t &&F) noexcept; + + // Dtor. + ~Invoker(void); + + // Enumeration of possible results of an invokation. + enum class Result { NoMatch, Invoked }; + + // Type alias for Result. + using result_t = Result; + + // Invoking F with Msg if the values stored in Msg are of proper types as + // arguments for F. + result_t operator()(const Message &Msg) const noexcept; +}; + +template +Invoker>::Invoker( + function_t &&F) noexcept : F(F) { + ASSERT(bool(F)); // Sanity check. + LOG_TRACE("Creating Invoker for arguments with Token(" + + to_string(TypeToken::Value) + ")"); +} + +template +Invoker>::~Invoker(void) { + LOG_TRACE("Destroying Invoker"); +} + +template +template +void Invoker>::invokeFunction( + Seq, const args_t &Args) const noexcept { + ASSERT(sizeof...(S) == std::tuple_size::value); // Sanity check. + F(std::get(Args)...); +} + +template +typename Invoker>::result_t +Invoker>:: +operator()(const Message &Msg) const noexcept { + using Matcher = MsgMatcher; + if (Matcher::doesStronglyMatch(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; + } +} + +} // End namespace rosa + +#endif // ROSA_CORE_INVOKER_HPP + diff --git a/include/rosa/core/Message.hpp b/include/rosa/core/Message.hpp new file mode 100644 index 0000000..ed03649 --- /dev/null +++ b/include/rosa/core/Message.hpp @@ -0,0 +1,97 @@ +/******************************************************************************* + * + * File: Message.hpp + * + * Contents: Declaration of Message base-class. + * + * Copyright 2017 + * + * Author: David Juhasz (david.juhasz@tuwien.ac.at) + * + ******************************************************************************/ + +#ifndef ROSA_CORE_MESSAGE_HPP +#define ROSA_CORE_MESSAGE_HPP + +#include "rosa/support/log.h" +#include "rosa/support/type_token.hpp" + +namespace rosa { + +// Message interface. The interface provides means to check the type of the +// stored values, but actual data is to be managed by derived implementations. +// Messages are immutable data objects, obtaining their data upon creation and +// providing only constant references for the stored values. +// NOTE: Any reference obtained from a Message instance remains valid only as +// long as the owning Message object is not destroyed. +class Message { +protected: + // Ctor. + // NOTE: No implementation for empty list. + template Message(Type, Ts...) noexcept; + +private: + // No copy and move. + Message(const Message &) = delete; + Message(Message &&) = delete; + Message &operator=(const Message &) = delete; + Message &operator=(Message &&) = delete; + + // A valid, non-empty token representing the types of the values stored in + // the Message. + const Token T; + + // The number of types encoded in T. + const size_t N; + +public: + // Virtual dtor. + virtual ~Message(void); + + // Tells how many values are in the message. + inline size_t size(void) const noexcept; + + // Tells if the value in position Pos is of type Type. + // NOTE: Token encodes atoms as AtomValue and not directly AtomConstants. + // PRE: Pos < size() + template bool isTypeAt(const size_t Pos) const noexcept; + + // Gives a constant reference of the value of type Type in position Pos. + // PRE: Pos < size() && isTypeAt(Pos) + template + const Type &getValueAt(const size_t Pos) const noexcept; + +protected: + // Provides an untyped pointer for the value in position Pos. + // PRE: Pos < size() + virtual const void *getPointerTo(const size_t Pos) const noexcept = 0; +}; + +template +Message::Message(Type, Ts...) noexcept : T(TypeToken::Value), + N(lengthOfToken(T)) { + ASSERT(!emptyToken(T) && validToken(T) && + lengthOfToken(T) == (1 + sizeof...(Ts))); // Sanity check. + LOG_TRACE("Creating Message with Token(" + to_string(T) + ")"); +} + +size_t Message::size(void) const noexcept { return N; } + +template +bool Message::isTypeAt(const size_t Pos) const noexcept { + ASSERT(Pos < size()); + Token T_ = T; // NOLINT + dropNOfToken(T_, Pos); + return isHeadOfTokenTheSameType(T_); +} + +template +const Type &Message::getValueAt(const size_t Pos) const noexcept { + ASSERT(Pos < size() && isTypeAt(Pos)); + return *static_cast(getPointerTo(Pos)); +} + +} // End namespace rosa + +#endif // ROSA_CORE_MESSAGE_HPP + diff --git a/include/rosa/core/MessageMatcher.hpp b/include/rosa/core/MessageMatcher.hpp new file mode 100644 index 0000000..f7d5466 --- /dev/null +++ b/include/rosa/core/MessageMatcher.hpp @@ -0,0 +1,159 @@ +/******************************************************************************* + * + * File: MessageMatcher.hpp + * + * Contents: Implementation of MessageMatcher. + * + * Copyright 2017 + * + * Author: David Juhasz (david.juhasz@tuwien.ac.at) + * + ******************************************************************************/ + +#ifndef ROSA_CORE_MESSAGEMATCHER_HPP +#define ROSA_CORE_MESSAGEMATCHER_HPP + +#include "rosa/core/Message.hpp" +#include + +namespace rosa { + +// Template class with static functions type-checking a Message instance and +// extracting stored values from Message instsances into std::tuple instances +// with matching type arguments. +template struct MessageMatcher; + +// Definition of MessageMatcher for non-empty lists of types, like Message +// itself. +template +struct MessageMatcher> { + // Tells if stored values in Msg are matching types given as + // TypeList, considering exact AtomConstants instead of AtomValues. + static inline bool doesStronglyMatch(const Message &Msg) noexcept; + + // Gives a std::tuple with references to values stored in a type-matching Msg. + // PRE: doesStronglyMatch(Msg) + static inline std::tuple + extractedValues(const Message &Msg) noexcept; +}; + +// Convenience template alias turning a list of types into a TypeList for +// MessageMatcher. +template +using MsgMatcher = MessageMatcher>; + +// Nested namespace with implementation for features of MessageMatcher, +// consider it private. +namespace { + +// Helper struct implementing type-checking and value extraction for +// MessageMatcher. +template struct MessageMatcherImpl; + +// Specialization handling the empty list of types. +template <> struct MessageMatcherImpl { + static inline bool doesStronglyMatchFrom(const Message &Msg, + const size_t Pos) noexcept { + // Matching EmptyTypeList only if reached the end of the stored types. + return Pos == Msg.size(); + } + + static inline std::tuple<> extractedValuesFrom(const Message &Msg, + const size_t Pos) noexcept { + // It is valid to extract an empty list only if we reached the end of + // stored values. + ASSERT(doesStronglyMatchFrom(Msg, Pos)); + return std::tie(); + } +}; + +// Specialization handling an AtomValue in the head. +template +struct MessageMatcherImpl, Ts...>> { + + static inline bool doesHeadStronglyMatchAt(const Message &Msg, + const size_t Pos) noexcept { + // Matching an AtomConstant in the head if there is a type stored at Pos, + // the stored type is AtomValue, and the corresponding value matches the + // AtomValue V. + return Pos < Msg.size() && Msg.isTypeAt(Pos) && + Msg.getValueAt(Pos) == V; + } + + static inline bool doesStronglyMatchFrom(const Message &Msg, + const size_t Pos) noexcept { + // Matching a non-empty list if the head is matching and the rest of the + // list is matching. + return doesHeadStronglyMatchAt(Msg, Pos) && + MessageMatcherImpl>::doesStronglyMatchFrom(Msg, + Pos + 1); + } + + static inline std::tuple &, const Ts &...> + extractedValuesFrom(const Message &Msg, const size_t Pos) noexcept { + // Extracting for a non-empty list with a matching AtomConstant in the head + // by getting the encoded AtomConstant and concatenating it with values + // extracted for the rest of the list. + ASSERT(doesHeadStronglyMatchAt(Msg, Pos)); + return std::tuple_cat( + std::tie(AtomConstant::Value), + MessageMatcherImpl>::extractedValuesFrom(Msg, Pos + 1)); + } + +}; + +// Specialization handling an regular builtin type (not an AtomConstant) in the +// head. +template +struct MessageMatcherImpl> { + + static inline bool doesHeadStronglyMatchAt(const Message &Msg, + const size_t Pos) noexcept { + // Matching the head if there is a type stored at Pos, and the stored type + // is T. + return Pos < Msg.size() && Msg.isTypeAt(Pos); + } + + static inline bool doesStronglyMatchFrom(const Message &Msg, + const size_t Pos) noexcept { + // Matching a non-empty list if the head is matching and the rest of the + // list is matching. + return doesHeadStronglyMatchAt(Msg, Pos) && + MessageMatcherImpl>::doesStronglyMatchFrom(Msg, + Pos + 1); + } + + static inline std::tuple + extractedValuesFrom(const Message &Msg, const size_t Pos) noexcept { + // Extracting for a non-empty list with a matching head by getting the + // value for the head and concatenating it with values extracted for the + // rest of the list. + ASSERT(doesHeadStronglyMatchAt(Msg, Pos)); + return std::tuple_cat( + std::tie(Msg.getValueAt(Pos)), + MessageMatcherImpl>::extractedValuesFrom(Msg, Pos + 1)); + } +}; + +} // End namespace + +template +bool MessageMatcher>::doesStronglyMatch( + const Message &Msg) noexcept { + // NOTE: Fail quick on the size. + return (1 + sizeof...(Ts)) == Msg.size() && + MessageMatcherImpl>::doesStronglyMatchFrom(Msg, 0); +} + +template +std::tuple +MessageMatcher>::extractedValues( + const Message &Msg) noexcept { + ASSERT(doesStronglyMatch(Msg)); + return MessageMatcherImpl>::extractedValuesFrom(Msg, 0); +} + +} // End namespace rosa + +#endif // ROSA_CORE_MESSAGEMATCHER_HPP + diff --git a/lib/core/CMakeLists.txt b/lib/core/CMakeLists.txt index 6aff479..5d7c6a9 100644 --- a/lib/core/CMakeLists.txt +++ b/lib/core/CMakeLists.txt @@ -1,8 +1,9 @@ add_library(ROSACore Unit.cpp System.cpp SystemImpl.cpp + Message.cpp ) ROSA_add_library_dependencies(ROSACore ROSASupport) diff --git a/lib/core/Message.cpp b/lib/core/Message.cpp new file mode 100644 index 0000000..a216a1e --- /dev/null +++ b/lib/core/Message.cpp @@ -0,0 +1,22 @@ +/******************************************************************************* + * + * File: Message.cpp + * + * Contents: Implementation of Message base-class. + * + * Copyright 2017 + * + * Author: David Juhasz (david.juhasz@tuwien.ac.at) + * + ******************************************************************************/ + +#include "rosa/core/Message.hpp" + +namespace rosa { + +Message::~Message(void) { + LOG_TRACE("Destroying Message"); +} + +} // End namespace rosa +