Commit 639575c9 authored by Hrvoje's avatar Hrvoje

Merge branch 'usability/patterns_overhaul' into 'develop'

Patterns overhaul

Closes #8, #13, #14, and #17

See merge request aconno/aconnoLibs/Patterns!47
parents f0fd16e0 aeeb6353
THIS_PATH := $(realpath $(dir $(realpath $(lastword $(MAKEFILE_LIST)))))
export PROJ_SRC := $(PROJ_SRC) \
$(THIS_PATH)/src/error/Error.cpp
$(THIS_PATH)/src/Error.cpp
export PROJ_INC := $(PROJ_INC) \
$(THIS_PATH)/include
# Patterns
---
# Essential
Library of generalized patterns.
## Available patterns
- C callable - function wrapper - [documentation](doc/CCallable.md)
- Endians - helper functions - [documentation](doc/Endians.md)
- Error - error handling - [documentation](doc/Error.md)
- Future - helper functions - [documentation](doc/Future.md)
- Lifetime list - container - [documentation](doc/LifetimeList.md)
- Observer - design pattern - [documentation](doc/Observer.md)
- Patterns port - function wrappers - [documentation](doc/PatternsPort.md)
- Essential port - function wrappers - [documentation](doc/EssentialPort.md)
- Scope exit - helper functions - [documentation](doc/ScopeExit.md)
- Singleton - design pattern - [documentation](doc/Singleton.md)
- State machine - design pattern - [documentation](doc/StateMachine.md)
......
# Function wrappers - CCallable
---
## Files
- `CCallable.hpp` - Declaration of wrapping functions.
- `CCallable.cxx` - Implementation of wrapping functions.
## About
wrapCCallable is a function forwarding C callbacks with environment (or context) pointer
to object member functions.
### Example
```cpp
typedef void (CCallback*) (int a, void* environment, char b);
// c libn function to hook the callback
void hookCallback(CCallback cb, void* environment);
// your class
class Foo
{
// member function callback handler
void cbHandler(int a, char b);
/**
* more application stuff
*/
};
// class instance
Foo f1{};
// somewhere in your code
hookCallback(Patterns::CCallable<Foo, void(int, char), &Foo::cbHandler, int, char>, &f1);
```
## How to use
......@@ -4,7 +4,7 @@
## Files
- `Error.hpp` - Basic error definitions and functions.
- `Error.hpp` - Universal error handling module.
## About
......
# Function wrappers - patterns port
# Function wrappers - essential port
---
## Files
- `PatternsPort.hpp` - Port specific functions that need to be supplied by the platform abstraction
layer.
- `EssentialPort.hpp` - Port specific functions that need to be supplied by the platform abstraction
layer.
## About
In order to execute error handling and testing properly, a few forward declarations
have to be made. The developer of the abstraction layer has to supply a PatternsPort.cpp
have to be made. The developer of the abstraction layer has to supply a EssentialPort.cpp
that translates these functions for his or hers platform.
## How to use
......
# Helper functions - future
---
## Files
- `Future.hpp` - Missing functions from the C++ version from the future.
## About
TODO
## How to use
TODO
\ No newline at end of file
......@@ -28,7 +28,7 @@ A list has to be emptied before it is destroyed.
The first example shows how to use LifetimeList to make a list of all objects of a class:
```cpp
#include <patterns/lifetime_list/LifetimeList.hpp>
#include <container/LifetimeList.hpp>
/**
* class that has a registry of all instances
......@@ -45,9 +45,9 @@ class MyClass
* A static list inside this class would not be able to guarantee,
* that the list is constructed before it is used the first time.
*/
static Patterns::LifetimeList<MyClass&>& getInstance()
static Container::LifetimeList<MyClass&>& getInstance()
{
static Patterns::LifetimeList<MyClass&> instances{};
static Container::LifetimeList<MyClass&> instances{};
return instances;
}
......@@ -56,13 +56,13 @@ class MyClass
*
* It does not guarantee order of construction!
*/
// static Patterns::LifetimeList<MyClass&> instances;
// static Container::LifetimeList<MyClass&> instances;
private:
Patterns::LifetimeList<MyClass&>::Node node; /**< the node belongs to the object */
Container::LifetimeList<MyClass&>::Node node; /**< the node belongs to the object */
};
Patterns::LifetimeList<MyClass&> MyClass::instances{};
Container::LifetimeList<MyClass&> MyClass::instances{};
// instantiate as many as you want, they will all be registered in instances.
static MyClass myObjects[100];
......@@ -70,9 +70,9 @@ static MyClass myObjects[100];
The second example shows how to use it as a stand alone list
```cpp
#include <patterns/lifetime_list/LifetimeList.hpp>
#include <container/LifetimeList.hpp>
Patterns::LifetimeList<int> iList{}; /**< generic list of integers */
Container::LifetimeList<int> iList{}; /**< generic list of integers */
void main()
{
......
......@@ -20,7 +20,7 @@ A high-level module has to contain a call to _void Test::Base::runAllTests()_ in
- There should be one test class per class that is tested.
- All test classes should be in the Test namespace.
- Do not build parallel namespace trees (e.g. Patterns::MyClass and Test::Patterns::MyClass). Otherwise it will be difficult to get the Patterns::MyClass from the test class context.
- Do not build parallel namespace trees (e.g. Essential::MyClass and Test::Essential::MyClass). Otherwise it will be difficult to get the Essential::MyClass from the test class context.
- Use the eager loading singleton pattern descried above. This way, you do not have to bother about instantiating the test. It also enables other tests to reference your test as a prerequisite.
- Extend the Test::Base class to create your test.
- The testing is done in the _void runInternal()_ virtual function. Implement that. Call the assign function whenever you want your test to check a value. The errMsg is written when the check fails.
......
/**
* @file
*
* @brief Basic error definitions and functions.
* @brief Universal error handling module.
*
* @note Namespace of Error (it should be Patterns::Error) is not synchronized with the file
* structure because it is used very often. Short reference is preferred over a long one in
* this unique case.
*
* @copyright aconno GmbH (c) 2020
* @copyright Copyright (C) 2021 aconno GmbH. Full licence notice is in the LICENCE file.
*/
#ifndef __ERRORS_HPP__
#define __ERRORS_HPP__
#pragma once
#include <cstdint>
#include <cstdlib>
......@@ -20,13 +15,21 @@
#define __FILENAME__ (strrchr(__FILE__, '/') ? strrchr(__FILE__, '/') + 1 : __FILE__)
/**
* @brief If the err is not a success, stop the execution of the program, and handle the error.
*
* @details All errors are handled in the same way. If NDEBUG is not defined, function will trigger
* a fault breakpoint, and enter a infinite loop. Alternatively, when NDEBUG is defined, function
* will log and restart ne MCU. This macro is typically used from the constructor.
*/
#define CHECK_ERROR(err) \
Error::internalCheck(err, \
static_cast<uint32_t>(__LINE__), \
static_cast<const char* const>(__FILENAME__))
/**
* @brief Macro that checks an error code and returns the current function if an error occured.
* @brief Macro that checks an error code and returns from the current function if an error
* occurred.
*/
#define RETURN_ON_ERROR(err) \
{ \
......@@ -47,79 +50,61 @@
}
/**
* @brief Basic error definitions and functions. Pure static class. Non instantiable.
* @brief Basic error definitions and functions. Pure static class, i.e., non-instantiable.
*/
class Error
{
public:
Error() = delete;
Error() = delete;
Error(const Error& other) = delete;
Error& operator=(const Error& other) = delete;
/**
* @brief Error code. Used to have a common way of error handling throughout the whole code
* base.
*
*/
enum [[nodiscard]] Code {None = 0,
NotInitialized,
NotFound,
ChecksumFailed,
AlreadyInit,
UsingDestroyed,
Alignment,
TooLarge,
OutOfResources,
Memory,
Internal,
InvalidUse,
Busy,
StackCorrupt,
InvalidParameter,
Timeout,
MinReached,
MaxReached,
OutOfBounds,
Empty,
Full,
Lifetime,
CommunicationFailed,
PureVirtual,
Acknowledgement,
SizeMissmatch,
Signal,
CoprocessorResponse,
NotInitialized,
NotFound,
ChecksumFailed,
AlreadyInit,
UsingDestroyed,
Alignment,
TooLarge,
OutOfResources,
Memory,
Internal,
InvalidUse,
Busy,
StackCorrupt,
InvalidParameter,
Timeout,
MinReached,
MaxReached,
OutOfBounds,
Empty,
Full,
Lifetime,
CommunicationFailed,
PureVirtual,
Acknowledgement,
SizeMissmatch,
Signal,
CoprocessorResponse,
Unknown,
Count};
Unknown,
/* Count is the upper bound of the enum value. It shouldn't be
* used directly.
*/
Count};
/**
* @brief Tests an error code.
* If code is anything else than success, restarts.
*
* @warning do not directly use. Use CHECK_ERROR() instead.
*
* @param code
*/
static void internalCheck(Code code, uint32_t lineNumber, std::string_view fileName);
static void internalCheck(Code errorCode, uint32_t lineNumber, std::string_view fileName);
/**
* @brief Get the Description string for the given error code.
*
* @warning Only use with #ifdef DEBUG safeguards. Function is only
* available when compiling with DEBUG macro
*
* @param code
* @return Description of the error code given in the parameter and defined in the lookup table
* Error::Descriptions.
*/
static std::string_view getDescription(Code code);
static std::string_view getDescription(Code errorCode);
private:
/**
* @brief String error descriptors.
*
*/
static std::string_view Descriptions[];
/** String error descriptions. */
static std::string_view descriptions[];
};
#endif //__ERRORS_HPP__
......@@ -9,7 +9,7 @@
#ifndef __LIFETIME_LIST_HPP__
#define __LIFETIME_LIST_HPP__
namespace Patterns
namespace Container
{
template<class T>
class LifetimeList;
......@@ -18,7 +18,7 @@ class LifetimeList;
#include <iterator>
#include <memory>
namespace Patterns
namespace Container
{
/**
* @brief Linked list that add's and remove's with Node lifetime.
......@@ -118,9 +118,9 @@ private:
Node* root; /**< Root(first) node of the collection. */
Node* tail; /**< Tail(last) node of the collection. */
};
} // namespace Patterns
} // namespace Container
#include "../../../src/lifetime_list/LifetimeList.cxx"
#include "../../../src/lifetime_list/Iterator.cxx"
#include "../../../src/lifetime_list/Node.cxx"
#include "../../src/container/LifetimeList.cxx"
#include "../../src/container/lifetime_list/Iterator.cxx"
#include "../../src/container/lifetime_list/Node.cxx"
#endif //__LIFETIME_LIST_HPP__
\ No newline at end of file
......@@ -11,7 +11,7 @@
#include <tuple>
namespace Patterns
namespace Essential
{
/**
* @brief Object that invokes a given function when going out of scope.
......@@ -50,7 +50,7 @@ public:
template<class CallableT, class... CallableArgsT>
ScopeExit<CallableT, CallableArgsT...> make_scopeExit(CallableT callable, CallableArgsT&&... args);
} // namespace Patterns
} // namespace Essential
#include "../../../src/scope_exit/ScopeExit.cxx"
#include "../../src/essential/ScopeExit.cxx"
#endif //__SCOPE_EXIT_HPP__
\ No newline at end of file
......@@ -9,11 +9,11 @@
#ifndef __BITFIELD_HPP__
#define __BITFIELD_HPP__
#include "patterns/endians/Endians.hpp"
#include "patterns/error/Error.hpp"
#include "essential/endians/Endians.hpp"
#include "Error.hpp"
#include <array>
namespace Patterns::Endians
namespace Essential::Endians
{
/**
* @brief Anonymous namespace to hide internal workings of this lib.
......@@ -84,9 +84,9 @@ enum class BitOrder
* "no type named ‘type’".
*
* @example Simple usage to read and write an unsigned int:
* #include <patterns/endians/Bitfield.hpp>
* #include <endians/Bitfield.hpp>
*
* using MostSignificantBit = Patterns::Endians::Bitfield<unsigned int, 7, 1>;
* using MostSignificantBit = Endians::Bitfield<unsigned int, 7, 1>;
* uint8_t data1 = 0b1000000;
* auto value = MostSignificantBit::get(data1); // will return 1
* CHECK_ERROR(MostSignificantBit::set(data1, false)); // will make data1 == 0u
......@@ -178,13 +178,13 @@ struct Bitfield
}
}
if (endianess == Patterns::Endians::ByteOrder::big)
if (endianess == Endians::ByteOrder::big)
{
Patterns::Endians::bigToMachine(result);
Endians::bigToMachine(result);
}
else
{
Patterns::Endians::littleToMachine(result);
Endians::littleToMachine(result);
}
return result;
......@@ -208,13 +208,13 @@ struct Bitfield
"Bitfield getter out of boundries");
static_assert(sizeof(T) * 8 >= size, "Bitfield size too big for chosen data type");
if (endianess == Patterns::Endians::ByteOrder::big)
if (endianess == Endians::ByteOrder::big)
{
Patterns::Endians::machineToBig(value);
Endians::machineToBig(value);
}
else
{
Patterns::Endians::machineToLittle(value);
Endians::machineToLittle(value);
}
auto ref = reinterpret_cast<const uint8_t*>(&value);
......@@ -307,5 +307,5 @@ struct Bitfield
return set<arraySize, byteOffset>(data.data(), value);
}
};
} // namespace Patterns::Endians
} // namespace Essential::Endians
#endif //__BITFIELD_HPP__
\ No newline at end of file
......@@ -23,7 +23,7 @@
#error unknown endianness
#endif
namespace Patterns::Endians
namespace Essential::Endians
{
/**
* @brief Enumeration values for endianness.
......@@ -47,7 +47,7 @@ constexpr void bigToMachine(T& val);
template<class T>
constexpr void littleToMachine(T& val);
} // namespace Patterns::Endians
} // namespace Essential::Endians
#include "../../../src/endians/Endians.cxx"
#include "../../../src/essential/endians/Endians.cxx"
#endif //__ENDIANS_HPP__
\ No newline at end of file
......@@ -3,15 +3,10 @@
*
* @brief Port specific functions that need to be supplied by the platform abstraction layer.
*
* @note Namespace of this file (it should be Patterns::) is not synchronized with the file
* structure because the functions it provides should be implemented by the platform
* abstraction layer (PAL).
*
* @copyright aconno GmbH (c) 2020
*/
#ifndef __PATTERNS_PORT_HPP__
#define __PATTERNS_PORT_HPP__
#pragma once
#include <cstdint>
#include <string_view>
......@@ -19,10 +14,10 @@
/**
* @brief Port specific functions.
*
* @warning Only the header file is in the Patterns library.
* @warning Only the header file is in the Essential library.
* Implement your own cpp in Platform AL!
*/
namespace PAL
namespace PAL::Essential
{
/**
* @brief Restarts the MCU.
......@@ -54,5 +49,4 @@ void disableInterrupts();
* @brief Use to forcibly stop execution at a certain point.
*/
void faultBreakpoint();
} // namespace PAL
#endif //__PATTERNS_PORT_HPP__
\ No newline at end of file
} // namespace PAL
\ No newline at end of file
......@@ -9,18 +9,18 @@
#ifndef __OBSERVABLE_HPP__
#define __OBSERVABLE_HPP__
namespace Patterns
namespace Pattern
{
template<class... ObservableParamTypes>
class Observable;
}
#include "patterns/error/Error.hpp"
#include "patterns/observer/Observer.hpp"
#include "Error.hpp"
#include "pattern/Observer.hpp"
#include <forward_list>
namespace Patterns
namespace Pattern
{
/**
* @brief Inherit this class, to supply an observable hook.
......@@ -74,9 +74,9 @@ protected:
*/
void trigger(ObservableParamTypes...);
};
} // namespace Patterns
} // namespace Pattern
// template classes need this
#include "../../../src/observer/Observable.cxx"
#include "../../src/pattern/Observable.cxx"
#endif //__OBSERVABLE_HPP__
\ No newline at end of file
......@@ -9,15 +9,15 @@
#ifndef __OBSERVER_HPP__
#define __OBSERVER_HPP__
namespace Patterns
namespace Pattern
{
template<class... ObservableParamTypes>
class Observer;
}
#include "patterns/observer/Observable.hpp"
#include "pattern/Observable.hpp"
namespace Patterns
namespace Pattern
{
/**
* @brief Handles Observables.
......@@ -58,8 +58,8 @@ public:
Observable<ObservableParamTypes...>& observable;
};
} // namespace Patterns
} // namespace Pattern
#include "../../../src/observer/Observer.cxx"
#include "../../src/pattern/Observer.cxx"
#endif //__OBSERVER_HPP__
\ No newline at end of file
......@@ -9,7 +9,7 @@
#ifndef __STATE_MACHINE_HPP__
#define __STATE_MACHINE_HPP__
namespace Patterns
namespace Pattern
{
/**
* @brief Function that executes something within the given context.
......@@ -21,9 +21,9 @@ using Activity = void (TContext::*)();
template<class TContext>
class StateMachine;
} // namespace Patterns
} // namespace Pattern
namespace Patterns
namespace Pattern
{
/**
* @brief Container for state pattern. Contains states and controls them.
......@@ -90,7 +90,7 @@ protected:
virtual Activity<TContext> getNextState(TContext& context,
const Activity<TContext> currentState) = 0;
};
} // namespace Patterns
} // namespace Pattern
#include "../../../src/state_machine/StateMachine.cxx"
#include "../../src/pattern/StateMachine.cxx"
#endif //__STATE_MACHINE_HPP__
\ No newline at end of file
/**
* @file
*
* @brief Functions for making C++ member functions callable in C.
*
* @copyright aconno GmbH (c) 2020
*/
#ifndef __C_CALLABLE_HPP__
#define __C_CALLABLE_HPP__
namespace Patterns
{
/**
* @brief Wraps a member function to be used in a C callback with env context.
* The template args are redundant, that shall be fixed as soon as a
* solution is found.
*
* @tparam T class of the member function
* @tparam FctType type of the fct without class, e.g. "int(int, int)"
* @tparam fct member function to be wrapped
* @tparam FirstArgTypes arguments of the member function
* @tparam SecondArgTypes arguments of the member function
* @param args1 args generated by the C lib for this callback
* @param env environment passed by callback (this ptr). Has to be passed when
* registering to C callback.
* @param args2 args generated by the C lib for this callback.
* @return decltype(auto)
*/
template<class T, class FctType, FctType T::*fct, class... FirstArgTypes, class... SecondArgTypes>
decltype(auto) wrapCCallable(FirstArgTypes&&... args1, void* env, SecondArgTypes&&... args2);
/**
* @brief Wrap a member function in a static function call.
* At the moment giving M and ArgTypes is redundant to the type of fct.
* This shall be fixed as soon as there is a better way to do this.
*
* @tparam M type of the function fct, e.g. 'int(int, char*)'
* @tparam fct pointer to the member function
* @tparam ArgTypes type of the arguments of fct, e.g. 'int, char*' for the
* above example
* @param args
* @return decltype(auto) return type is the same as fct return type.
*/
template<class ClassToBeSingle, class M, M ClassToBeSingle::*fct, class... ArgTypes>
decltype(auto) wrapSingleton(ArgTypes&&... args);
} // namespace Patterns
#include "../../../src/c_callable/CCallable.cxx"
#endif //__C_CALLABLE_HPP__
\ No newline at end of file
/**
* @file
*
* @brief Contains declarations that compensate for missing C++ version from the future.
*
* @copyright aconno GmbH (c) 2020
*/
#ifndef __FUTURE_HPP__
#define __FUTURE_HPP__
#include <functional>
namespace Patterns::Future
{
/**
* @brief Poor mans C++14 compatible invoke.
*/
template<typename Fn,
typename... Args,
std::enable_if_t<std::is_member_pointer<std::decay_t<Fn>> {}, int> = 0>
constexpr decltype(auto)
invoke(Fn&& f, Args&&... args) noexcept(noexcept(std::mem_fn(f)(std::forward<Args>(args)...)))
{
return std::mem_fn(f)(std::forward<Args>(args)...);
}
/**
* @brief Poor mans C++14 compatible invoke.
*/
template<typename Fn,
typename... Args,
std::enable_if_t<!std::is_member_pointer<std::decay_t<Fn>> {}, int> = 0>
constexpr decltype(auto) invoke(Fn&& f, Args&&... args) noexcept(
noexcept(std::forward<Fn>(f)(std::forward<Args>(args)...)))
{
return std::forward<Fn>(f)(std::forward<Args>(args)...);
}
} // namespace Patterns::Future
#endif //__FUTURE_HPP__
\ No newline at end of file
/**
* @file
*
* @brief Basic error handling implementation.
* @brief Universal error handling module.
*
* @copyright aconno GmbH (c) 2020
* @copyright Copyright (C) 2021 aconno GmbH. Full licence notice is in the LICENCE file.
*/
#include "patterns/error/Error.hpp"
#include "patterns/PatternsPort.hpp"
#include "Error.hpp"
#include "pal/essential/EssentialPort.hpp"
// clang-format off
std::string_view Error::Descriptions[] = {
std::string_view Error::descriptions[] = {
[None] = "Operation successful.",
[NotInitialized] = "Module or object not initialized.",
[NotFound] = "Resource(s) not found.",
......@@ -44,62 +44,74 @@ std::string_view Error::Descriptions[] = {
};
// clang-format on
std::string_view Error::getDescription(Code code)
/**
* @brief Gets the description of the error code given.
*
* @param[in] errorCode Error code for which to get the description.
*
* @return Description of the error code.
*/
std::string_view Error::getDescription(Code errorCode)
{