diff --git a/docs/Issues.rst b/docs/Issues.rst index 1e5a920..3f2a4b9 100755 --- a/docs/Issues.rst +++ b/docs/Issues.rst @@ -1,45 +1,54 @@ ================================================================== Known Issues with the Current Implementation of the RoSA Framework ================================================================== .. contents:: :local: TODO ==== * Project logo - `docs/_themes/rosa-theme/static/logo.png` * License? * Packaging with `CPack `_. * What about design documentation on the basics of RoSA? * What about testing the framework? Known Issues ============ * CMake * VS2017 generates intermediate files for the `ZERO_CHECK` project out of the build directory, see `CMake issue #16458`_. * C++ * Mangled names of function pointers with non-throwing exception specification in function signature will change in C++17. That renders binaries generated with C++14 and C++17 incompatible (for linking). * Since version 4.0.0, Clang warns about this compatibility issue as part of `-Wc++1z-compat`. That warning is turned off in the build scripts. * The langauge standard for building RoSA libraries and applications needs to be lockstepped: now use C++14 only and step to C++17 later when it is properly supported by all major compilers. * Doxygen * There are some strange warnings reported by doxygen when generating documentation. * There are some entities for which no or partial documentation is generated, but no indication of any problem is reported by doxygen. +* clang-tidy + + * Clang-tidy reports warnings about `noexcept` marking for the move + constructor and move assignment operator of `rosa::Optional` in some + situations when the template with the non-specialized argument list is used + -- for example, in the file `example/deluxe-interface/deluxe-interface.cpp`. + However, the condition for the `noexcept` marking should be met and the + warning is pointless. + .. _CMake issue #16458: https://gitlab.kitware.com/cmake/cmake/issues/16458 diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt index f39a687..276f940 100644 --- a/examples/CMakeLists.txt +++ b/examples/CMakeLists.txt @@ -1,7 +1,8 @@ # Add the different subdirectories add_subdirectory(basic-system) add_subdirectory(type-facilities) add_subdirectory(messaging) add_subdirectory(messaging-system) add_subdirectory(agent-modules) +add_subdirectory(deluxe-interface) diff --git a/examples/deluxe-interface/CMakeLists.txt b/examples/deluxe-interface/CMakeLists.txt new file mode 100755 index 0000000..7b5017b --- /dev/null +++ b/examples/deluxe-interface/CMakeLists.txt @@ -0,0 +1,4 @@ +add_executable(deluxe-interface deluxe-interface.cpp) +ROSA_add_library_dependencies(deluxe-interface ROSAConfig) +ROSA_add_library_dependencies(deluxe-interface ROSADeluxe) + diff --git a/examples/deluxe-interface/deluxe-interface.cpp b/examples/deluxe-interface/deluxe-interface.cpp new file mode 100755 index 0000000..6f3b8e2 --- /dev/null +++ b/examples/deluxe-interface/deluxe-interface.cpp @@ -0,0 +1,312 @@ +//===-- examples/deluxe-interface/deluxe-interface.cpp ----------*- C++ -*-===// +// +// The RoSA Framework +// +//===----------------------------------------------------------------------===// +/// +/// \file examples/deluxe-interface/deluxe-interface.cpp +/// +/// \author David Juhasz (david.juhasz@tuwien.ac.at) +/// +/// \date 2017 +/// +/// \brief A simple example on the \c rosa::deluxe::DeluxeContext and related +/// classes. +//===----------------------------------------------------------------------===// + +#include "rosa/agent/Abstraction.hpp" +#include "rosa/agent/Confidence.hpp" + +#include "rosa/config/version.h" + +#include "rosa/deluxe/DeluxeContext.hpp" + +#include "rosa/support/csv/CSVReader.hpp" +#include "rosa/support/csv/CSVWriter.hpp" + +#include + +using namespace rosa; +using namespace rosa::agent; +using namespace rosa::deluxe; +using namespace rosa::terminal; + +/// Paths for the CSV files for simulation. +/// +///@{ +const std::string HRCSVPath = "HR.csv"; +const std::string BRCSVPath = "BR.csv"; +const std::string SpO2CSVPath = "SpO2.csv"; +const std::string BPSysCSVPath = "BPSys.csv"; +const std::string BodyTempCSVPath = "BodyTemp.csv"; +const std::string ScoreCSVPath = "Score.csv"; +///@} + +/// How many cycles of simulation to perform. +const size_t NumberOfSimulationCycles = 16; + +/// Warning levels for abstraction. +enum WarningScore { No = 0, Low = 1, High = 2, Emergency = 3 }; + +/// Helper function creating a deluxe agent for pre-processing sensory values. +/// +/// Received values are first validated for confidence. Values which the +/// validator does not mark confident are ignored. Confident values are +/// abstracted into a \c WarningScore value, which is the result of the +/// processing function. +/// +/// \note The result, \c WarningScore, is returned as \c uint32_t because +/// enumeration types are not integrated into built-in types. Hence, a master +/// to these agents receives its input as \c uint32_t values, and may cast them +/// to \c WarningScore explicitly. +/// +/// \tparam T type of values to receive from the sensor +/// +/// \param C the deluxe context to create the agent in +/// \param Name name of the new agent +/// \param CC confidence validator to use +/// \param A abstraction to use +/// +/// \return handle for the new agent +template +AgentHandle createLowLevelAgent(std::unique_ptr &C, + const std::string &Name, + const Confidence &CC, + const Abstraction &A) { + using handler = DeluxeAgent::D; + using result = Optional; + return C->createAgent( + Name, handler([&, Name](std::pair I) -> result { + LOG_INFO_STREAM << "\n******\n" + << Name << " " << (I.second ? "" : "") + << " value: " << I.first << "\n******\n"; + return (I.second && CC(I.first)) ? result(A(I.first)) : result(); + })); +} + +int main(void) { + LOG_INFO_STREAM + << '\n' + << library_string() << " -- " << Color::Red << "deluxe-interface example" + << Color::Default << '\n' + << Color::Yellow + << "CSV files are read from and written to the current working directory." + << Color::Default << '\n'; + + std::unique_ptr C = DeluxeContext::create("Deluxe"); + + // + // Create deluxe sensors. + // + LOG_INFO("Creating sensors."); + + // All sensors are created without defining a normal generator function, but + // with the default value of the second argument. That, however, requires the + // data type to be explicitly defined. This is good for simulation only. + AgentHandle HRSensor = C->createSensor("HR Sensor"); + AgentHandle BRSensor = C->createSensor("BR Sensor"); + AgentHandle SpO2Sensor = C->createSensor("SpO2 Sensor"); + AgentHandle BPSysSensor = C->createSensor("BPSys Sensor"); + AgentHandle BodyTempSensor = C->createSensor("BodyTemp Sensor"); + + // + // Create functionalities. + // + LOG_INFO("Creating Functionalities for Agents."); + + // + // Define confidence validators. + // + // Lower bounds are inclusive and upper bounds are exclusive. + + Confidence HRConfidence(0, 501); + Confidence BRConfidence(0, 301); + Confidence SpO2Confidence(0, 101); + Confidence BPSysConfidence(0,501); + Confidence BodyTempConfidence(-60, + nextRepresentableFloatingPoint(50.0f)); + + // + // Define abstractions. + // + + RangeAbstraction HRAbstraction( + {{{0, 40}, Emergency}, + {{40, 51}, High}, + {{51, 60}, Low}, + {{60, 100}, No}, + {{100, 110}, Low}, + {{110, 129}, High}, + {{129, 200}, Emergency}}, + Emergency); + + RangeAbstraction BRAbstraction({{{0, 9}, High}, + {{9, 14}, No}, + {{14, 20}, Low}, + {{20, 29}, High}, + {{29, 50}, Emergency}}, + Emergency); + + RangeAbstraction SpO2Abstraction({{{1, 85}, Emergency}, + {{85, 90}, High}, + {{90, 95}, Low}, + {{95, 100}, No}}, + Emergency); + + RangeAbstraction BPSysAbstraction( + {{{0, 70}, Emergency}, + {{70, 81}, High}, + {{81, 101}, Low}, + {{101, 149}, No}, + {{149, 169}, Low}, + {{169, 179}, High}, + {{179, 200}, Emergency}}, + Emergency); + + RangeAbstraction BodyTempAbstraction( + {{{0, 28}, Emergency}, + {{28, 32}, High}, + {{32, 35}, Low}, + {{35, 38}, No}, + {{38, 39.5}, High}, + {{39.5, 100}, Emergency}}, + Emergency); + + // + // Create low-level deluxe agents with \c createLowLevelAgent. + // + LOG_INFO("Creating low-level agents."); + + AgentHandle HRAgent = + createLowLevelAgent(C, "HR Agent", HRConfidence, HRAbstraction); + AgentHandle BRAgent = + createLowLevelAgent(C, "BR Agent", BRConfidence, BRAbstraction); + AgentHandle SpO2Agent = + createLowLevelAgent(C, "SpO2 Agent", SpO2Confidence, SpO2Abstraction); + AgentHandle BPSysAgent = + createLowLevelAgent(C, "BPSys Agent", BPSysConfidence, BPSysAbstraction); + AgentHandle BodyTempAgent = createLowLevelAgent( + C, "BodyTemp Agent", BodyTempConfidence, BodyTempAbstraction); + + // + // Connect sensors to low-level agents. + // + LOG_INFO("Connect sensors to their corresponding low-level agents."); + + C->connectSensor(HRAgent, 0, HRSensor, "HR Sensor Channel"); + C->connectSensor(BRAgent, 0, BRSensor, "BR Sensor Channel"); + C->connectSensor(SpO2Agent, 0, SpO2Sensor, "SpO2 Sensor Channel"); + C->connectSensor(BPSysAgent, 0, BPSysSensor, "BPSys Sensor Channel"); + C->connectSensor(BodyTempAgent, 0, BodyTempSensor, "BodyTemp Sensor Channel"); + + // + // Create a high-level deluxe agent. + // + LOG_INFO("Create high-level agent."); + + // The new agent logs its input values and results in the the sum of them. + AgentHandle BodyAgent = C->createAgent( + "Body Agent", + DeluxeAgent::D( + [](std::pair HR, std::pair BR, + std::pair SpO2, std::pair BPSys, + std::pair BodyTemp) -> Optional { + LOG_INFO_STREAM << "\n*******\nBody Agent trigged with values:\n" + << (HR.second ? "" : "") + << " HR warning score: " << HR.first << "\n" + << (BR.second ? "" : "") + << " BR warning score: " << BR.first << "\n" + << (SpO2.second ? "" : "") + << " SpO2 warning score: " << SpO2.first << "\n" + << (BPSys.second ? "" : "") + << " BPSys warning score: " << BPSys.first << "\n" + << (BodyTemp.second ? "" : "") + << " BodyTemp warning score: " << BodyTemp.first + << "\n******\n"; + return {HR.first + BR.first + SpO2.first + BPSys.first + + BodyTemp.first}; + })); + + // + // Connect low-level agents to the high-level agent. + // + LOG_INFO("Connect low-level agents to the high-level agent."); + + C->connectAgents(BodyAgent, 0, HRAgent, "HR Agent Channel"); + C->connectAgents(BodyAgent, 1, BRAgent, "BR Agent Channel"); + C->connectAgents(BodyAgent, 2, SpO2Agent, "SpO2 Agent Channel"); + C->connectAgents(BodyAgent, 3, BPSysAgent, "BPSys Agent Channel"); + C->connectAgents(BodyAgent, 4, BodyTempAgent, "BodyTemp Agent Channel"); + + // + // For simulation output, create a logger agent writing the output of the + // high-level agent into a CSV file. + // + LOG_INFO("Create a logger agent."); + + // Create CSV writer. + std::ofstream ScoreCSV(ScoreCSVPath); + csv::CSVWriter ScoreWriter(ScoreCSV); + + // The agent writes each new input value into a CSV file and produces nothing. + AgentHandle LoggerAgent = C->createAgent( + "Logger Agent", + DeluxeAgent::D( + [&ScoreWriter](std::pair Score) -> Optional { + if (Score.second) { + // The state of \p ScoreWriter is not checked, expecting good. + ScoreWriter << Score.first; + } + return {}; + })); + + // + // Connect the high-level agent to the logger agent. + // + LOG_INFO("Connect the high-level agent to the logger agent."); + + C->connectAgents(LoggerAgent, 0, BodyAgent, "Body Agent Channel"); + + // + // Do simulation. + // + LOG_INFO("Setting up and performing simulation."); + + // + // Initialize deluxe context for simulation. + // + + C->initializeSimulation(); + + // + // Open CSV files and register them for their corresponding sensors. + // + + // Type aliases for iterators. + using CSVInt = csv::CSVFlatIterator; + using CSVFloat = csv::CSVFlatIterator; + + std::ifstream HRCSV(HRCSVPath); + C->registerSensorValues(HRSensor, CSVInt(HRCSV), CSVInt()); + + std::ifstream BRCSV(BRCSVPath); + C->registerSensorValues(BRSensor, CSVInt(BRCSV), CSVInt()); + + std::ifstream SpO2CSV(SpO2CSVPath); + C->registerSensorValues(SpO2Sensor, CSVInt(SpO2CSV), CSVInt()); + + std::ifstream BPSysCSV(BPSysCSVPath); + C->registerSensorValues(BPSysSensor, CSVInt(BPSysCSV), CSVInt()); + + std::ifstream BodyTempCSV(BodyTempCSVPath); + C->registerSensorValues(BodyTempSensor, CSVFloat(BodyTempCSV), CSVFloat()); + + // + // Simulate. + // + + C->simulate(NumberOfSimulationCycles); + + return 0; +} diff --git a/include/rosa/support/csv/CSVReader.hpp b/include/rosa/support/csv/CSVReader.hpp new file mode 100755 index 0000000..cd625b4 --- /dev/null +++ b/include/rosa/support/csv/CSVReader.hpp @@ -0,0 +1,373 @@ +//===-- rosa/support/csv/CSVReader.hpp --------------------------*- C++ -*-===// +// +// The RoSA Framework +// +//===----------------------------------------------------------------------===// +/// +/// \file rosa/support/csv/CSVReader.hpp +/// +/// \author David Juhasz (david.juhasz@tuwien.ac.at) +/// +/// \date 2017 +/// +/// \brief Facitilities to read CSV files. +/// +/// \note The implementation is based on the solution at +/// https://stackoverflow.com/a/1120224 +/// +//===----------------------------------------------------------------------===// + +#ifndef ROSA_SUPPORT_CSV_CSVREADER_HPP +#define ROSA_SUPPORT_CSV_CSVREADER_HPP + +#include "rosa/support/debug.hpp" + +#include +#include +#include + +namespace rosa { +namespace csv { + +/// Anonymous namespace providing implementation details for +/// \c rosa::csv::CSVIterator, consider it private. +namespace { + +/// Provides facility for parsing values from one row CSV data. +/// +/// \tparam T type of values to parse from the line +/// \tparam IsSignedInt if \p T is a signed integral type, always use default +/// \tparam IsUnsignedInt if \p T is an unsigned integral type, always use +/// default +/// \tparam IsFloat if \p T is a floating-point type, always use default +/// \tparam IsString if \p T is \c std::string, always use default +/// +/// \note Specializations of this `struct` are provided for arithmentic types +/// and \c std::string. +template ::value && + std::is_signed::value), + bool IsUnsignedInt = + (std::is_integral::value && std::is_unsigned::value), + bool IsFloat = std::is_floating_point::value, + bool IsString = std::is_same::value> +struct CSVRowParser; + +/// Specialization for signed integral types. +/// +/// \tparam T type of values to parse from the line +/// +/// \pre \p T is a signed integral type:\code +/// std::is_integral::value && std::is_signed::value +/// \code +template struct CSVRowParser { + STATIC_ASSERT((std::is_integral::value && std::is_signed::value), + "wrong type"); // Sanity check. + + /// Parses a given row of CSV data into a given container. + /// + /// \p Data is cleared and then filled with values parsed from \p LineStream. + /// Entries in the line are to be separated by commas, the character `,`. A + /// trailing comma results in an empty entry at the end of the line. No empty + /// entry should be present otherwise. + /// + /// \param [in,out] LineStream the line to parse + /// \param [in,out] Data the container to store the parsed values + static void parse(std::stringstream &LineStream, std::vector &Data) { + std::string Cell; + Data.clear(); + while (std::getline(LineStream, Cell, ',')) { + Data.push_back(std::stoll(Cell)); + } + // This checks for a trailing comma with no data after it. + if (!LineStream && Cell.empty()) { + // If there was a trailing comma then add an empty element. + Data.push_back(0); + } + } +}; + +/// Specialization for unsigned integral types. +/// +/// \tparam T type of values to parse from the line +/// +/// \pre \p T is an unsigned integral type:\code +/// std::is_integral::value && std::is_unsigned::value +/// \code +template struct CSVRowParser { + STATIC_ASSERT((std::is_integral::value && std::is_unsigned::value), + "wrong type"); // Sanity check. + + /// Parses a given row of CSV data into a given container. + /// + /// \p Data is cleared and then filled with values parsed from \p LineStream. + /// Entries in the line are to be separated by commas, the character `,`. A + /// trailing comma results in an empty entry at the end of the line. No empty + /// entry should be present otherwise. + /// + /// \param [in,out] LineStream the line to parse + /// \param [in,out] Data the container to store the parsed values + static void parse(std::stringstream &LineStream, std::vector &Data) { + std::string Cell; + Data.clear(); + while (std::getline(LineStream, Cell, ',')) { + Data.push_back(std::stoull(Cell)); + } + // This checks for a trailing comma with no data after it. + if (!LineStream && Cell.empty()) { + // If there was a trailing comma then add an empty element. + Data.push_back(0); + } + } +}; + +/// Specialization for floating-point types. +/// +/// \tparam T type of values to parse from the line +/// +/// \pre \p T is a floating-point type:\code +/// std::is_floating_point::value +/// \code +template struct CSVRowParser { + STATIC_ASSERT((std::is_floating_point::value), + "wrong type"); // Sanity check. + + /// Parses a given row of CSV data into a given container. + /// + /// \p Data is cleared and then filled with values parsed from \p LineStream. + /// Entries in the line are to be separated by commas, the character `,`. A + /// trailing comma results in an empty entry at the end of the line. No empty + /// entry should be present otherwise. + /// + /// \param [in,out] LineStream the line to parse + /// \param [in,out] Data the container to store the parsed values + static void parse(std::stringstream &LineStream, std::vector &Data) { + std::string Cell; + Data.clear(); + while (std::getline(LineStream, Cell, ',')) { + Data.push_back(std::stold(Cell)); + } + // This checks for a trailing comma with no data after it. + if (!LineStream && Cell.empty()) { + // If there was a trailing comma then add an empty element. + Data.push_back(0); + } + } +}; + +/// Specialization for \c std::string. +/// +/// \tparam T type of values to parse from the line +/// +/// \pre \p T is \c std::string:\code +/// std::is_same::value +/// \code +template struct CSVRowParser { + STATIC_ASSERT((std::is_same::value), + "wrong type"); // Sanity check. + + /// Parses a given row of CSV data into a given container. + /// + /// \p Data is cleared and then filled with values parsed from \p LineStream. + /// Entries in the line are to be separated by commas, the character `,`. A + /// trailing comma results in an empty entry at the end of the line. No empty + /// entry should be present otherwise. + /// + /// \param [in,out] LineStream the line to parse + /// \param [in,out] Data the container to store the parsed values + static void parse(std::stringstream &LineStream, std::vector &Data) { + std::string Cell; + Data.clear(); + while (std::getline(LineStream, Cell, ',')) { + Data.push_back(Cell); + } + // This checks for a trailing comma with no data after it. + if (!LineStream && Cell.empty()) { + // If there was a trailing comma then add an empty element. + Data.push_back(""); + } + } +}; + +/// Parses and stores entries from a row of CSV data. +/// +/// \tparam T type of values to parse and store, i.e. entries in the row +/// +/// \note The implementation relies on \c rosa::csv::CSVRowParser, which is +/// implemented only for `arithmetic` types -- signed and unsigned integral and +/// floating-point types -- and for \c std::string. Those are the valid values +/// for \p T. +template +class CSVRow { +public: + /// Gives a constant reference for an entry at a given position of the row. + /// + /// \note No bounds checking is performed. + /// + /// \param Index the position of the entry + /// + /// \return constant reference for the stored entry at position \p Index + const T &operator[](const size_t Index) const noexcept { + return Data[Index]; + } + + /// Tells the number of entries stored in the row. + /// + /// \return number of stored entries. + size_t size(void) const noexcept { return Data.size(); } + + /// Parses and stores one row of CSV data. + /// + /// The function reads one line from \p Str and parses it into + /// \c rosa::csv::CSVRow::Data using \c rosa::csv::CSVRowParser. + /// + /// \param [in,out] Str input stream of a CSV file + void readNextRow(std::istream &Str) { + std::string Line; + std::getline(Str, Line); + std::stringstream LineStream(Line); + CSVRowParser::parse(LineStream, Data); + } + +private: + std::vector Data; ///< Stores parsed entries +}; + +/// Reads a row of CSV data into \c rosa::csv::CSVRow. +/// +/// The next line is read from \p Str by calling +/// \c rosa::csv::CSVRow::readNextRow on \p Data. +/// +/// \note A CSV file should contain no empty lines. +/// +/// \param [in,out] Str input stream of a CSV file +/// \param [in,out] Data object to read the next line into +/// +/// \return \p Str after reading one line from it +template +std::istream &operator>>(std::istream &Str, CSVRow &Data) { + Data.readNextRow(Str); + return Str; +} + +} // End namespace + +/// Provides `InputIterator` features for iterating over a CSV file in a +/// flat way. +/// +/// The iterator hides rows of the CSV file, and iterates over the entries +/// row-by-row. +/// +/// \note A CSV file should contain no empty lines. +/// +/// \tparam T type of values to iterate over, i.e. entries in the CSV file. +/// +/// \note The implementation relies on \c rosa::csv::CSVRow, which in turn +/// relies on \c rosa::csv::CSVRowParser, which is implemented only for +/// `arithmetic` types -- signed and unsigned integral types and floating-point +/// types -- and for \c std::string. Those are the valid values for \p T. +template +class CSVFlatIterator { +public: + /// \defgroup CSVIteratorTypedefs + /// + /// Standard `typedef`s for iterators. + /// + ///@{ + typedef std::input_iterator_tag + iterator_category; ///< Category of the iterator. + typedef T value_type; ///< Type of values iterated over. + typedef std::size_t difference_type; ///< Type to identify distance. + typedef T *pointer; ///< Pointer to the type iterated over. + typedef T &reference; ///< Reference to the type iterated over. + ///@} + + /// Creates a new instance. + /// + /// \param [in,out] S input stream to iterate over + CSVFlatIterator(std::istream &S) : Str(S.good() ? &S : nullptr) { ++(*this); } + + /// Creates an empty new instance. + CSVFlatIterator(void) noexcept : Str(nullptr) {} + + /// Pre-increment operator. + /// + /// The implementation moves over the entries in the current row and advances + /// to the next row when the end of the current row is reached. If the end of + /// the input stream is reached, the operator becomes empty and has no + /// further effect. + /// + /// \return \p this object after incrementing it. + CSVFlatIterator &operator++() { + if (Pos < Row.size()) { + ++Pos; + } else if (Str) { + if (!((*Str) >> Row)) { + Str = nullptr; + } else { + Pos = 0; + } + } + return *this; + } + + /// Post-increment operator. + /// + /// The implementation uses the pre-increment operator and returns a copy of + /// the original state of \p this object. + /// + /// \return \p this object before incrementing it. + CSVFlatIterator operator++(int) { + CSVFlatIterator Tmp(*this); + ++(*this); + return Tmp; + } + + /// Returns a constant reference to the current entry. + /// + /// \note Should not dereference the iterator when it is empty. + /// + /// \return constant reference to the current entry. + const T &operator*(void)const noexcept { + return Row[Pos]; + } + + /// Returns a constant pointer to the current entry. + /// + /// \note Should not dereference the iterator when it is empty. + /// + /// \return constant pointer to the current entry. + const T *operator->(void)const noexcept { return &Row[Pos]; } + + /// Tells if \p this object is equal to another one. + /// + /// Two \c rosa::csv::CSVReader instances are equal if and only if they are + /// the same or both are empty. + /// + /// \param RHS other object to compare to + /// + /// \return whether \p this object is equal with \p RHS + bool operator==(const CSVFlatIterator &RHS) const noexcept { + return ((this == &RHS) || ((this->Str == nullptr) && (RHS.Str == nullptr))); + } + + /// Tells if \p this object is not equal to another one. + /// + /// \see rosa::csv::CSVReader::operator== + /// + /// \param RHS other object to compare to + /// + /// \return whether \p this object is not equal with \p RHS. + bool operator!=(const CSVFlatIterator &RHS) const noexcept { + return !((*this) == RHS); + } + +private: + std::istream *Str; ///< Input stream of a CSV file to iterate over. + CSVRow Row; ///< Content of the current row iterating over. + size_t Pos; ///< Current position within the current row. +}; + +} // End namespace csv +} // End namespace rosa + +#endif // ROSA_SUPPOR_CSV_CSVREADER_HPP diff --git a/include/rosa/support/csv/CSVWriter.hpp b/include/rosa/support/csv/CSVWriter.hpp new file mode 100755 index 0000000..17046a4 --- /dev/null +++ b/include/rosa/support/csv/CSVWriter.hpp @@ -0,0 +1,99 @@ +//===-- rosa/support/csv/CSVWriter.hpp --------------------------*- C++ -*-===// +// +// The RoSA Framework +// +//===----------------------------------------------------------------------===// +/// +/// \file rosa/support/csv/CSVWriter.hpp +/// +/// \author David Juhasz (david.juhasz@tuwien.ac.at) +/// +/// \date 2017 +/// +/// \brief Facitilities to write CSV files. +/// +//===----------------------------------------------------------------------===// + +#ifndef ROSA_SUPPORT_CSV_CSVWRITER_HPP +#define ROSA_SUPPORT_CSV_CSVWRITER_HPP + +#include + +namespace rosa { +namespace csv { + +/// Provides facilities to write values into a CSV file. +/// +/// The writer emits a comma, the character `,`, between each written values. +/// The resulted stream is a flat CSV file as it consists of onlyone row, no new +/// line is emitted. +/// +/// \tparam T type of values to write +template +class CSVWriter { +public: + /// Creates a new instance. + /// + /// \param [in,out] S output stream to write to + /// + /// \note The writer operates on non-binary outputs as long as \p S is in + /// good state. + CSVWriter(std::ostream &S) + : Str(S.good() && !(S.flags() & std::ios::binary) ? &S : nullptr), + IsFirst(true) {} + + /// Tells if the last operation was successful. + /// + /// \return if the last operation was successful + bool good(void) const noexcept { + return Str != nullptr; + } + + /// Writes an entry to the output stream. + /// + /// The implementation does anything only if the last operation was + /// successful. If so, \p V is written to \c rosa::csv::CSVWriter::Str. + /// The emitted value is preceded with a comma if the actual call is not the + /// first one for \p this object. Success of the operation is checked at the + /// end. + /// + /// \param V value to write + void write(const T &V) { + if (Str) { + if (!IsFirst) { + *Str << ','; + } else { + IsFirst = false; + } + *Str << V; + if (!Str->good()) { + Str = nullptr; + } + } + } + +private: + std::ostream *Str; ///< Output stream to write to. + bool IsFirst; ///< Denotes if the next write would be the first one. +}; + +/// Writes a value to a CSV file with \c rosa::csv::CSVWriter. +/// +/// \see rosa::csv::CSVWriter +/// +/// \tparam T type of value to write +/// +/// \param [in,out] W object to write with +/// \param [in,out] V value to write +/// +/// \return \p W after writing \p V with it +template +CSVWriter &operator<<(CSVWriter &W, const T& V) { + W.write(V); + return W; +} + +} // End namespace csv +} // End namespace rosa + +#endif // ROSA_SUPPORT_CSV_CSVWRITER_HPP diff --git a/include/rosa/support/csv/namespace.h b/include/rosa/support/csv/namespace.h new file mode 100755 index 0000000..85ad49e --- /dev/null +++ b/include/rosa/support/csv/namespace.h @@ -0,0 +1,25 @@ +//===-- rosa/support/csv/namespace.h ----------------------------*- C++ -*-===// +// +// The RoSA Framework +// +//===----------------------------------------------------------------------===// +/// +/// \file rosa/support/csv/namespace.h +/// +/// \author David Juhasz (david.juhasz@tuwien.ac.at) +/// +/// \date 2017 +/// +/// \brief Documentation for the namespace \c rosa::csv. +/// +//===----------------------------------------------------------------------===// + +#ifndef ROSA_SUPPORT_CSV_NAMESPACE_H +#define ROSA_SUPPORT_CSV_NAMESPACE_H + +namespace rosa { +/// Provides facilities to work with CSV files. +namespace csv {} +} // End namespace rosa + +#endif // ROSA_SUPPORT_CSV_NAMESPACE_H diff --git a/include/rosa/support/math.hpp b/include/rosa/support/math.hpp index 768079f..f88a13d 100644 --- a/include/rosa/support/math.hpp +++ b/include/rosa/support/math.hpp @@ -1,35 +1,57 @@ //===-- rosa/support/math.hpp -----------------------------------*- C++ -*-===// // // The RoSA Framework // //===----------------------------------------------------------------------===// /// /// \file rosa/support/math.hpp /// /// \author David Juhasz (david.juhasz@tuwien.ac.at) /// /// \date 2017 /// /// \brief Math helpers. /// //===----------------------------------------------------------------------===// #ifndef ROSA_SUPPORT_MATH_HPP #define ROSA_SUPPORT_MATH_HPP +#include #include +#include +#include namespace rosa { /// Computes log base 2 of a number. /// /// \param N the number to compute log base 2 for /// /// \return log base 2 of \p N constexpr size_t log2(const size_t N) { return ((N < 2) ? 1 : 1 + log2(N / 2)); } +/// Tells the next representable floating point value. +/// +/// \tparam T type to operate on +/// +/// \note The second type argument enforces \p T being a floating point type, +/// always use the default value! +/// +/// \param V value to which find the next representable one +/// +/// \return the next representable value of type \p T after value \p V +/// +/// \pre Type \p T must be a floating point type, which is enforced by +/// `std::enable_if` in the second type argument. +template ::value>> +T nextRepresentableFloatingPoint(const T V) { + return std::nextafter(V, std::numeric_limits::infinity()); +} + } // End namespace rosa #endif // ROSA_SUPPORT_MATH_HPP diff --git a/lib/support/CMakeLists.txt b/lib/support/CMakeLists.txt index 1a7ea9b..6e191ef 100644 --- a/lib/support/CMakeLists.txt +++ b/lib/support/CMakeLists.txt @@ -1,32 +1,38 @@ set(LIB_INCLUDE_DIR ${ROSA_MAIN_INCLUDE_DIR}/rosa/support) add_library(ROSASupport ${LIB_INCLUDE_DIR}/debug.hpp debug.cpp ${LIB_INCLUDE_DIR}/terminal_colors.h terminal_colors.cpp ${LIB_INCLUDE_DIR}/log.h log.cpp ${LIB_INCLUDE_DIR}/math.hpp math.cpp ${LIB_INCLUDE_DIR}/type_helper.hpp type_helper.cpp ${LIB_INCLUDE_DIR}/types.hpp types.cpp ${LIB_INCLUDE_DIR}/atom.hpp atom.cpp ${LIB_INCLUDE_DIR}/type_pair.hpp type_pair.cpp ${LIB_INCLUDE_DIR}/type_list.hpp type_list.cpp ${LIB_INCLUDE_DIR}/squashed_int.hpp squashed_int.cpp ${LIB_INCLUDE_DIR}/type_numbers.hpp type_numbers.cpp ${LIB_INCLUDE_DIR}/type_token.hpp type_token.cpp ${LIB_INCLUDE_DIR}/tokenized_storages.hpp tokenized_storages.cpp ${LIB_INCLUDE_DIR}/sequence.hpp sequence.cpp + ${LIB_INCLUDE_DIR}/csv/namespace.h + csv/namespace.cpp + ${LIB_INCLUDE_DIR}/csv/CSVReader.hpp + csv/CSVReader.cpp + ${LIB_INCLUDE_DIR}/csv/CSVWriter.hpp + csv/CSVWriter.cpp ) diff --git a/lib/support/csv/CSVReader.cpp b/lib/support/csv/CSVReader.cpp new file mode 100755 index 0000000..bf055fb --- /dev/null +++ b/lib/support/csv/CSVReader.cpp @@ -0,0 +1,20 @@ +//===-- support/csv/CSVReader.cpp -------------------------------*- C++ -*-===// +// +// The RoSA Framework +// +//===----------------------------------------------------------------------===// +/// +/// \file support/csv/CSVReader.cpp +/// +/// \author David Juhasz (david.juhasz@tuwien.ac.at) +/// +/// \date 2017 +/// +/// \brief Implementation for rosa/support/csv/CSVReader.hpp. +/// +/// \note Empty implementation, source file here to have a compile database +/// entry for rosa/support/csv/CSVReader.hpp. +/// +//===----------------------------------------------------------------------===// + +#include "rosa/support/csv/CSVReader.hpp" diff --git a/lib/support/csv/CSVWriter.cpp b/lib/support/csv/CSVWriter.cpp new file mode 100755 index 0000000..4310703 --- /dev/null +++ b/lib/support/csv/CSVWriter.cpp @@ -0,0 +1,20 @@ +//===-- support/csv/CSVWriter.cpp -------------------------------*- C++ -*-===// +// +// The RoSA Framework +// +//===----------------------------------------------------------------------===// +/// +/// \file support/csv/CSVWriter.cpp +/// +/// \author David Juhasz (david.juhasz@tuwien.ac.at) +/// +/// \date 2017 +/// +/// \brief Implementation for rosa/support/csv/CSVWriter.hpp. +/// +/// \note Empty implementation, source file here to have a compile database +/// entry for rosa/support/csv/CSVWriter.hpp. +/// +//===----------------------------------------------------------------------===// + +#include "rosa/support/csv/CSVWriter.hpp" diff --git a/lib/support/csv/namespace.cpp b/lib/support/csv/namespace.cpp new file mode 100755 index 0000000..744e6c1 --- /dev/null +++ b/lib/support/csv/namespace.cpp @@ -0,0 +1,20 @@ +//===-- support/csv/namespace.cpp -------------------------------*- C++ -*-===// +// +// The RoSA Framework +// +//===----------------------------------------------------------------------===// +/// +/// \file support/csv/namespace.cpp +/// +/// \author David Juhasz (david.juhasz@tuwien.ac.at) +/// +/// \date 2017 +/// +/// \brief Placeholder for rosa/support/csv/namespace.h. +/// +/// \note Empty implementation, source file here to have a compile database +/// entry for rosa/support/csv/namespace.h. +/// +//===----------------------------------------------------------------------===// + +#include "rosa/support/csv/namespace.h"