diff --git a/apps/sa-ews1/sa-ews1.cpp b/apps/sa-ews1/sa-ews1.cpp index 67a1ac6..23b02b8 100644 --- a/apps/sa-ews1/sa-ews1.cpp +++ b/apps/sa-ews1/sa-ews1.cpp @@ -1,316 +1,316 @@ //===-- apps/sa-ews1/sa-ews1.cpp --------------------------------*- C++ -*-===// // // The RoSA Framework -- Application SA-EWS1 // //===----------------------------------------------------------------------===// /// /// \file apps/sa-ews1/sa-ews1.cpp /// /// \author David Juhasz (david.juhasz@tuwien.ac.at) /// /// \date 2017-2019 /// /// \brief The application SA-EWS1 implements the case study from the paper: /// M. Götzinger, N. Taherinejad, A. M. Rahmani, P. Liljeberg, A. Jantsch, and /// H. Tenhunen: Enhancing the Early Warning Score System Using Data Confidence /// DOI: 10.1007/978-3-319-58877-3_12 //===----------------------------------------------------------------------===// #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; const std::string AppName = "SA-EWS1"; /// 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 << AppName << "app" << 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(AppName); // // 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.f, 28.f}, Emergency}, {{28.f, 32.f}, High}, {{32.f, 35.f}, Low}, {{35.f, 38.f}, No}, {{38.f, 39.5f}, High}, {{39.5f, 100.f}, 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; + using CSVInt = csv::CSVIterator; + using CSVFloat = csv::CSVIterator; 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/examples/CSVFiles/CMakeLists.txt b/examples/CSVFiles/CMakeLists.txt index fe4cdb3..cfc92e7 100644 --- a/examples/CSVFiles/CMakeLists.txt +++ b/examples/CSVFiles/CMakeLists.txt @@ -1,3 +1,3 @@ add_executable(csvfiles main.cpp) ROSA_add_library_dependencies(csvfiles ROSAConfig) -ROSA_add_library_dependencies(csvfiles ROSASupport) +ROSA_add_library_dependencies(csvfiles ROSADeluxe) diff --git a/examples/CSVFiles/main.cpp b/examples/CSVFiles/main.cpp index 6ca7342..23f3955 100644 --- a/examples/CSVFiles/main.cpp +++ b/examples/CSVFiles/main.cpp @@ -1,328 +1,328 @@ //===-- apps/sa-ews1/sa-ews1.cpp --------------------------------*- C++ -*-===// // // The RoSA Framework -- Application SA-EWS1 // //===----------------------------------------------------------------------===// /// /// \file apps/sa-ews1/sa-ews1.cpp /// /// \author David Juhasz (david.juhasz@tuwien.ac.at) /// /// \date 2017-2019 /// /// \brief The application SA-EWS1 implements the case study from the paper: /// M. Götzinger, N. Taherinejad, A. M. Rahmani, P. Liljeberg, A. Jantsch, and /// H. Tenhunen: Enhancing the Early Warning Score System Using Data Confidence /// DOI: 10.1007/978-3-319-58877-3_12 //===----------------------------------------------------------------------===// #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; const std::string AppName = "Test-CSVFiles"; /// Paths for the CSV files for simulation. /// ///@{ //const std::string HRCSVPath = "HR.csv"; const std::string HRCSVPathOld = "HR-New.csv"; const std::string HRCSVPath = "HR-New-Semicolon.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 << AppName << "app" << 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(AppName); // // 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.f, 28.f}, Emergency}, {{28.f, 32.f}, High}, {{32.f, 35.f}, Low}, {{35.f, 38.f}, No}, {{38.f, 39.5f}, High}, {{39.5f, 100.f}, 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. //parseparse 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; + using CSVInt = csv::CSVIterator; + using CSVFloat = csv::CSVIterator; //std::ifstream HRCSV(HRCSVPath); //CSVInt testIt(HRCSV); /* std::ifstream HRCSV(HRCSVPath); C->registerSensorValues(HRSensor, CSVInt(HRCSV, 5, false, ';', '\n'), CSVInt()); */ std::ifstream HRCSVOld(HRCSVPathOld); - C->registerSensorValues(HRSensor, CSVInt(HRCSVOld, 2), CSVInt()); + C->registerSensorValues(HRSensor, CSVInt(HRCSVOld/*, 2*/), CSVInt()); /* std::ifstream BRCSV(HRCSVPath); C->registerSensorValues(BRSensor, CSVInt(BRCSV, 4, false, ';', '\n'), CSVInt()); */ std::ifstream SpO2CSV(HRCSVPath); - C->registerSensorValues(SpO2Sensor, CSVInt(SpO2CSV, 2, false, ';', '\n'), CSVInt()); + C->registerSensorValues(SpO2Sensor, CSVInt(SpO2CSV/*, 2, false, ';', '\n'*/), CSVInt()); /* std::ifstream BPSysCSV(BPSysCSVPath); C->registerSensorValues(BPSysSensor, CSVInt(BPSysCSV), CSVInt());*/ std::ifstream BodyTempCSV(HRCSVPath); - C->registerSensorValues(BodyTempSensor, CSVFloat(BodyTempCSV, 2, false, ';', '\n'), CSVFloat()); + C->registerSensorValues(BodyTempSensor, CSVFloat(BodyTempCSV/*, 2, false, ';', '\n'*/), CSVFloat()); // // Simulate. // C->simulate(NumberOfSimulationCycles); return 0; } diff --git a/include/rosa/deluxe/DeluxeContext.hpp b/include/rosa/deluxe/DeluxeContext.hpp index 29794a1..646e866 100755 --- a/include/rosa/deluxe/DeluxeContext.hpp +++ b/include/rosa/deluxe/DeluxeContext.hpp @@ -1,294 +1,333 @@ //===-- rosa/deluxe/DeluxeContext.hpp ---------------------------*- C++ -*-===// // // The RoSA Framework // //===----------------------------------------------------------------------===// /// /// \file rosa/deluxe/DeluxeContext.hpp /// /// \author David Juhasz (david.juhasz@tuwien.ac.at) /// /// \date 2017-2019 /// /// \brief Public interface for the *deluxe interface* for working with agent /// systems. /// //===----------------------------------------------------------------------===// #ifndef ROSA_DELUXE_DELUXECONTEXT_HPP #define ROSA_DELUXE_DELUXECONTEXT_HPP #include "rosa/deluxe/DeluxeSystem.hpp" #include "rosa/support/types.hpp" #include #include #include /// Local helper macro to log and return a /// \c rosa::deluxe::DeluxeContext::ErrorCode value. /// /// Creates a debug message with the stringified value and returns the value. /// /// \param Err \c rosa::deluxe::DeluxeContext::ErrorCode value to log and /// return #define DCRETERROR(Err) \ { \ LOG_DEBUG(#Err); \ return Err; \ } namespace rosa { namespace deluxe { /// Defines the *deluxe interface*. class DeluxeContext { /// A system owned by \p this object. /// /// \note The reference is kept in a \c std::shared_ptr because of the member /// function \c rosa::deluxe::DeluxeContext::getSystem. std::shared_ptr System; /// References to all *sensors* and *agents* created by \p this object. std::set DeluxeUnits; public: /// Errors that may be resulted by some of the member functions of the class. enum struct ErrorCode { NoError, TypeMismatch, NotSensor, NotAgent, WrongPosition, AlreadyHasSlave, AlreadyHasMaster, AlreadyHasValueStream }; /// Returns a new instance of \c rosa::deluxe::DeluxeContext. /// /// \param Name name of the underlying \c rosa::DeluxeSystem /// /// \return \c std::unique_ptr for the new instance of /// \c rosa::deluxe::DeluxeContext with a new, empty \c rosa::DeluxeSystem static std::unique_ptr create(const std::string &Name) noexcept; private: /// Creates a new instance. /// /// \note Private constructor restricts instantiation to member functions of /// the class. /// /// \param Name name of the underlying \c rosa::MessagingSystem DeluxeContext(const std::string &Name) noexcept; public: /// Destroys \p this object. ~DeluxeContext(void) noexcept; /// Returns a reference for the underlying \c rosa::MessagingSystem. /// /// \note One cannot do much with a \c rosa::MessagingSystem currently, this /// is for future use. /// /// \return reference for the underlying \c rosa::MessagingSystem. std::weak_ptr getSystem(void) const noexcept; /// Creates a new *sensor* in the context of \p this object. /// /// \tparam T type of data the new *sensor* operates on /// + /// \todo The current implementation is an intermediate state, handles + /// built-in types and std::tuple, but the latter with with one element only. + /// Fix it when merging with DeluxeTuple. + /// /// \param Name name of the new *sensor* /// \param F function for the new *sensor* to generate the next value with /// during normal operation /// /// \note \p F is not used during simulation, in which case /// \c rosa::deluxe::DeluxeContext::registerSensorValues is used to register /// an alternative simulation data source with /// \c rosa::deluxe::DeluxeSensor::registerSimulationDataSource. One may /// safely keep relying on the default value of \p F as long as only /// simulation of the system is to be done. /// /// \return \c rosa::AgentHandle for the new *sensor* template AgentHandle createSensor(const std::string &Name, DeluxeSensor::D &&F = [](void) { return T(); }) noexcept; /// Creates a new *agent* in the context of \p this object. /// /// \tparam T type of data the new *agent* outputs /// \tparam As types of inputs the new *agent* takes /// /// \param Name name of the new *agent* /// \param F function for the new *agent* to process input values and /// generate output with /// /// \return \c rosa::AgentHandle for the new *agent* template AgentHandle createAgent(const std::string &Name, DeluxeAgent::D &&F) noexcept; /// Connectes a *sensor* to an *agent* in the context of \p this object. /// /// \param Agent the *agent* to connect to /// \param Pos the index of slot of \p Agent to connect \p Sensor to /// \param Sensor the *sensor* to connect /// \param Description optional textual description of the connection /// /// \return how successfull connecting \p Sensor to \p Agent at slot index /// \p Pos was /// /// \note The function may return the following /// \c rosa::deluxe::DeluxeContext::ErrorCode values: /// `ErrorCode` | Comment /// ----------- | ------- /// `NoError` | Success /// `NotAgent` | Referred \p Agent is not \c rosa::deluxe::DeluxeAgent /// `NotSensor` | Referred \p Sensor is not \c rosa::deluxe::DeluxeSensor /// `WrongPosition` | \p Pos is not a valid input position of \p Agent /// `TypeMismatch` | Expected input type at position \p Pos of \p Agent is other than the output type of \p Sensor /// `AlreadyHasSlave` | \p Agent at position \p Pos already has a *slave* registered /// `AlreadyHasMaster` | \p Sensor already has a *master* registered ErrorCode connectSensor(AgentHandle Agent, const size_t Pos, AgentHandle Sensor, const std::string &Description = "") noexcept; /// Connectes two *agents* in the context of \p this object. /// /// \param Master the *agent* to connect to /// \param Pos the index of slot of \p Master to connect \p Slave to /// \param Slave the *agent* to connect /// \param Description optional textual description of the connection /// /// \return how succesfull connecting \p Slave to \p Master at slot index /// \p Pos was /// /// \note The function may return the following /// \c rosa::deluxe::DeluxeContext::ErrorCode values: /// `ErrorCode` | Comment /// ----------- | ------- /// `NoError` | Success /// `NotAgent` | Referred \p Master or \p Slave is not \c rosa::deluxe::DeluxeAgent /// `WrongPosition` | \p Pos is not a valid input position of \p Master /// `TypeMismatch` | Expected input type at position \p Pos of \p Master is other than the output type of \p Slave /// `AlreadyHasSlave` | \p Master at position \p Pos already has a *slave* registered /// `AlreadyHasMaster` | \p Slave already has a *master* registered ErrorCode connectAgents(AgentHandle Master, const size_t Pos, AgentHandle Slave, const std::string &Description = "") noexcept; /// Initializes \c this object and others managed by \p this object for /// setting up and performing simulation. /// /// \see \c rosa::deluxe::DeluxeContext::registerSensorValues, /// \c rosa::deluxe::DeluxeContext::simulate /// /// Need to clear simulation data sources from all the *sensors*. void initializeSimulation(void) noexcept; /// Registers a stream providing values for a *sensor* during simulation. /// /// \tparam Iterator type of iterator providing values for \p Sensor /// \tparam T type of values \p Sensor is operating on, always use default! /// /// \param Sensor the *sensor* to register values for /// \param Start provides values for \p Sensor /// \param End denotes the end of stream of values /// \param Default value to be used when input stream is depleted during /// simulation /// /// \return how successful registering \p Source for \p Sensor /// /// \note The function may return the following /// \c rosa::deluxe::DeluxeContext::ErrorCode values: /// `ErrorCode` | Comment /// ----------- | ------- /// `NoError` | Success /// `TypeMismatch` | \p Sensor generates values of a type other than \p T /// `NotSensor` | Referred \p Sensor is not \c rosa::deluxe::DeluxeSensor /// `AlreadyHasValueStream` | \p Sensor already has simulation data source set template ErrorCode registerSensorValues(AgentHandle Sensor, Iterator &&Start, const Iterator &End, T Default = {}) noexcept; /// Performs the system contained by \p this object. /// /// The function performs \p NumCycles cycle of simulation. In each cycle, /// all the *agents* and *sensors* registered in /// \c rosa::deluxe::DeluxeContext::DeluxeUnits are trigged for execution. /// /// \param NumCycles number of cycles to perform /// /// \pre All the *sensors* in the system contained by \p this object generate /// their output from simulation data sources. void simulate(const size_t NumCycles) const noexcept; }; template AgentHandle DeluxeContext::createSensor(const std::string &Name, DeluxeSensor::D &&F) noexcept { AgentHandle H = System->createSensor(Name, std::move(F)); DeluxeUnits.emplace(H); return H; } template AgentHandle DeluxeContext::createAgent(const std::string &Name, DeluxeAgent::D &&F) noexcept { AgentHandle H = System->createAgent(Name, std::move(F)); DeluxeUnits.emplace(H); return H; } +/// Anonymous namespace for helper facilities, consider it private. +namespace { + +/// \todo Document... +template struct UnwrapSensorType { using Type = T; }; + +template struct UnwrapSensorType> { + using Type = typename std::tuple_element<0, std::tuple>::type; +}; + +} // End namespace + template DeluxeContext::ErrorCode DeluxeContext::registerSensorValues(AgentHandle Sensor, Iterator &&Start, const Iterator &End, T Default) noexcept { // Get the type of values provided by \p Iterator. STATIC_ASSERT((std::is_same::value), "type mismatch"); + constexpr bool isBuiltin = TypeListContains::Value; + if constexpr (!isBuiltin) { + // T must be a std::tuple. + STATIC_ASSERT(std::tuple_size::value == 1, "Wrong tuple type"); + STATIC_ASSERT( + (TypeListContains::type>::Value), + "Wrong element type in tuple"); + } + using TT = typename UnwrapSensorType::Type; // Make sure preconditions are met. if (!System->isDeluxeSensor(Sensor)) { DCRETERROR(ErrorCode::NotSensor); } auto S = System->getDeluxeSensor(Sensor); ASSERT(S); // Sanity check. - if (S->OutputType != TypeNumberOf::Value) { + if (S->OutputType != TypeNumberOf::Value) { DCRETERROR(ErrorCode::TypeMismatch); } else if (S->simulationDataSourceIsSet()) { DCRETERROR(ErrorCode::AlreadyHasValueStream); } // Register input stream. // \note Need to capture parameters by value so having local copies. S->registerSimulationDataSource( - DeluxeSensor::D([=](void) mutable noexcept { + DeluxeSensor::D([=](void) mutable noexcept { if (Start != End) { + TT Value; + if constexpr (isBuiltin) { + Value = *Start; + } else { + Value = std::get<0>(*Start); + } + ++Start; LOG_TRACE_STREAM << "Reading next value for sensor '" << S->FullName - << "': " << *Start << '\n'; - return *Start++; + << "': " << Value << '\n'; + return Value; } else { + TT Value; + if constexpr (isBuiltin) { + Value = Default; + } else { + Value = std::get<0>(Default); + } LOG_TRACE_STREAM << "Providing default value for sensor '" - << S->FullName << "': " << Default << '\n'; - return Default; + << S->FullName << "': " << Value << '\n'; + return Value; } })); return ErrorCode::NoError; } } // End namespace deluxe } // End namespace rosa // Undef local macro if not used in the corresponding implementation. #ifndef ROSA_LIB_DELUXE_DELUXECONTEXT_CPP #undef DCRETERROR #endif #endif // ROSA_DELUXE_DELUXECONTEXT_HPP diff --git a/include/rosa/support/csv/CSVReader.hpp b/include/rosa/support/csv/CSVReader.hpp index 6fbdd46..45c6e20 100755 --- a/include/rosa/support/csv/CSVReader.hpp +++ b/include/rosa/support/csv/CSVReader.hpp @@ -1,1108 +1,307 @@ //===-- rosa/support/csv/CSVReader.hpp --------------------------*- C++ -*-===// // // The RoSA Framework // //===----------------------------------------------------------------------===// /// /// \file rosa/support/csv/CSVReader.hpp /// /// \authors David Juhasz (david.juhasz@tuwien.ac.at), Edwin Willegger (edwin.willegger@tuwien.ac.at) /// /// \date 2017-2019 /// /// \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 "rosa/support/sequence.hpp" #include #include #include #include #include #include - namespace rosa { namespace csv { /// Indicating it the CSV file contains any header or not enum class HeaderInformation { HasHeader, HasNoHeader }; /// Anonymous namespace providing implementation details for /// \c rosa::csv::CSVIterator, consider it private. -namespace { +namespace { -/// Provides facility for parsing values from one row CSV data. +/// Provides facility for parsing one value from a string. /// -/// \tparam T type of values to parse from the line +/// \tparam T type of value to parse /// \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 +/// \note Specializations of this struct are provided for arithmentic types /// and \c std::string. -template ::value && - std::is_signed::value), +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 -/// \endcode -template struct CSVRowParser { - STATIC_ASSERT((std::is_integral::value && std::is_signed::value), - "wrong type"); // Sanity check. +struct ValueParser { - /// 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. /// - /// \note Parsed values are silently converted to type \p T. + /// \param Cell the \c std::string to parse /// - /// \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, char Delimeter = ',') { - std::string Cell; - Data.clear(); - while (std::getline(LineStream, Cell, Delimeter)) { - Data.push_back(static_cast(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); - } - } - - /// Parses a given column of a given row of CSV data into a given container. + /// \return the parsed value /// - /// \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. - /// - /// \note Parsed values are silently converted to type \p T. - /// - /// \param [in,out] LineStream the line to parse - /// \param [in,out] Data the container to store the parsed values - static void parseValue(std::stringstream &LineStream, std::vector &Data, size_t Column = 0, char Delimeter = ',') { - std::string Cell; - size_t currentColumn = 0; - Data.clear(); - while (std::getline(LineStream, Cell, Delimeter)) { - if(currentColumn == Column){ - Data.push_back(static_cast(std::stoll(Cell))); - break; - } - currentColumn = currentColumn + 1; - } - // 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); - } - } + /// \note The function silently fails if cannot parse \p Cell for type \p T. + static T parse(const std::string &Cell) noexcept; }; -/// 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 -/// \endcode -template struct CSVRowParser { - STATIC_ASSERT((std::is_integral::value && std::is_unsigned::value), +template +struct ValueParser { + 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. - /// - /// \note Parsed values are silently converted to type \p T. - /// - /// \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, char Delimeter = ',') { - std::string Cell; - Data.clear(); - while (std::getline(LineStream, Cell, Delimeter)) { - Data.push_back(static_cast(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); - } + static T parse(const std::string &Cell) noexcept { + return static_cast(std::stoll(Cell)); } +}; - /// Parses a given column of 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. - /// - /// \note Parsed values are silently converted to type \p T. - /// - /// \param [in,out] LineStream the line to parse - /// \param [in,out] Data the container to store the parsed values - static void parseValue(std::stringstream &LineStream, std::vector &Data, size_t Column = 0, char Delimeter = ',') { - std::string Cell; - size_t currentColumn = 0; - Data.clear(); - while (std::getline(LineStream, Cell, Delimeter)) { - if(currentColumn == Column){ - Data.push_back(static_cast(std::stoll(Cell))); - break; - } - currentColumn = currentColumn + 1; - } - // 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); - } +template +struct ValueParser { + STATIC_ASSERT((std::is_integral::value && std::is_unsigned::value), + "wrong type"); // Sanity check. + static T parse(const std::string &Cell) noexcept { + return static_cast(std::stoull(Cell)); } }; -/// 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 -/// \endcode -template struct CSVRowParser { +template +struct ValueParser { 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. - /// - /// \note Parsed values are silently converted to type \p T. - /// - /// \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, char Delimeter = ',') { - std::string Cell; - Data.clear(); - while (std::getline(LineStream, Cell, Delimeter)) { - Data.push_back(static_cast(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); - } - } - - /// Parses a given column of 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. - /// - /// \note Parsed values are silently converted to type \p T. - /// - /// \param [in,out] LineStream the line to parse - /// \param [in,out] Data the container to store the parsed values - static void parseValue(std::stringstream &LineStream, std::vector &Data, size_t Column = 0, char Delimeter = ',') { - std::string Cell; - size_t currentColumn = 0; - Data.clear(); - while (std::getline(LineStream, Cell, Delimeter)) { - if(currentColumn == Column){ - Data.push_back(static_cast(std::stold(Cell))); - break; - } - currentColumn = currentColumn + 1; - } - // 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); - } + static T parse(const std::string &Cell) noexcept { + return static_cast(std::stold(Cell)); } }; -/// 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 -/// \endcode -template struct CSVRowParser { +template +struct ValueParser { 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, char Delimeter = ',') { - std::string Cell; - Data.clear(); - while (std::getline(LineStream, Cell, Delimeter)) { - 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 a given column of 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. - /// - /// \note Parsed values are silently converted to type \p T. - /// - /// \param [in,out] LineStream the line to parse - /// \param [in,out] Data the container to store the parsed values - static void parseValue(std::stringstream &LineStream, std::vector &Data, size_t Column = 0, char Delimeter = ',') { - std::string Cell; - size_t currentColumn = 0; - Data.clear(); - while (std::getline(LineStream, Cell, Delimeter)) { - if(currentColumn == Column){ - Data.push_back(static_cast(std::stoll(Cell))); - break; - } - currentColumn = currentColumn + 1; - } - // 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); - } - } + static T parse(const std::string &Cell) noexcept { return Cell; } }; - -/// generic basic class for specialization of the function -/// it is not allowed to derive partial special functions from function templates -/// so thats the reason for this workaround. -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 CSVTupleRowParser; - - template struct CSVTupleRowParser { - STATIC_ASSERT((std::is_integral::value && std::is_signed::value), "wrong type"); - static T parseValue(const std::string &Cell) noexcept { - return static_cast(std::stoll(Cell)); - } - }; - - /// 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 +/// \tparam Ts types 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: - CSVRow() : isHeaderRead(false), Delimeter(','), - EndOfLine(','), Column(1){} - - /// Gives a constant reference for an entry at a given position of the row. +/// 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 Ts. +template class CSVRow { +private: + /// Parses a given row of CSV data into \c CSVRow::Data. /// - /// \note No bounds checking is performed. + /// \ CSVRow::Data is filled with values parsed from \p LineStream. Entries + /// in the line are to be separated by commas, the character `,`. /// - /// \param Index the position of the entry + /// \note Parsed values are silently converted to types \p Ts. /// - /// \return constant reference for the stored entry at position \p Index - //const T &operator[](const size_t Index) const noexcept { return Data[Index]; } - - template - void parseRow(std::stringstream &LineStream, Seq){ - STATIC_ASSERT(sizeof... (Ts) == sizeof... (S0), - "Not matching template arguments."); - - std::string Cell; - // Get fields and parse the values into the proper element of the tuple one - // by one in a fold expression. - ((std::getline(LineStream, Cell, ','), - std::get(Data) = CSVTupleRowParser::parseValue(((Cell)), ...))); - } - - /// Tells the number of entries stored in the row. + /// \note Parsing silently fails if values do not match \p Ts. /// - /// \return number of stored entries. - //size_t size(void) const noexcept { return Data.size(); } - - /// Parses and stores one row of CSV data. + /// \tparam S0 indices to access tuple elements. /// - /// 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] LineStream the line to parse /// - /// \param [in,out] Str input stream of a CSV file - template - void readNextRow(std::istream &Str) { - std::string Line; - std::getline(Str, Line); - std::stringstream LineStream(Line); - - RowNumber = RowNumber + 1; - parseRow(LineStream, typename GenSeq::Type()); - - // typename GenSeq::Type; - //CSVRowParser::parse(LineStream, Data, Delimeter); - } - - bool isNumeric(const std::string& input){ - return std::all_of(input.begin(), input.end(), ::isdigit); - } - - void readHeaderRow(std::istream &Str){ - std::string Line; - std::getline(Str, Line); - std::stringstream LineStream(Line); - - CSVRowParser::parse(LineStream, Header, Delimeter); - - RowNumber = RowNumber + 1; - isHeaderRead = true; - } - - inline bool isHeaderAlreadyRead(){ - return isHeaderRead; - } - - - HeaderInformation isHeaderSet(){ - return HeaderInfo; - } - - inline void setDelimeter(char Delimeter){ - this->Delimeter = Delimeter; - } - - inline char getDelimeter(){ - return this->Delimeter; - } - - inline void setEndOfLine(char EndOfLine){ - this->EndOfLine = EndOfLine; - } - - inline char getEndOfLine(){ - return this->EndOfLine; - } - - inline bool isThisFirstRow(){ - return this->isFirstRow; - } - - inline void setColumn(const size_t & Column){ - this->Column = Column; - } - - inline void setHeaderInfo(const HeaderInformation HeaderInfo){ - this->HeaderInfo = HeaderInfo; - } - - inline void setSkipRows(const size_t &SkipRows){ - this->SkipRows = SkipRows; - } - - inline const size_t & getSkipRows(){ - return this->SkipRows; - } - - inline uint64_t getRowNumber(){ - return this->RowNumber; - } - - inline const std::tuple &tuple(void) const noexcept { - return Data; + /// \note The last argument is used only to get \p S0, the actual value of + /// the parameter is ignored. + template + void parseRow(std::stringstream &LineStream, Seq) { + STATIC_ASSERT(sizeof...(Ts) == sizeof...(S0), + "Not matching template arguments."); + std::string Cell; + // Get fields and parse the values into the proper element of the tuple + // one by one in a fold expression. + ((std::getline(LineStream, Cell, ','), + std::get(Data) = ValueParser::parse(Cell)), + ...); } -private: - std::tuple Data; ///< Stores parsed entries - uint64_t RowNumber; ///< Current row number - std::vector Header; ///< Stores the header entries if available - char Delimeter; ///< Stores the delimeter between data entries - char EndOfLine; ///< Stores the end of line character - size_t Column; ///< Stores the column to get the data out of the row - HeaderInformation HeaderInfo; ///< Indicates if CSV file contains a header row (expected first row to be the header). - size_t SkipRows; ///< Number of Rows to skip at the beginning of the file. - bool isHeaderRead; ///< Indicates if header was read -}; - -/// Parses and stores entries from a row of CSV data. -/// It parses an entire row and takes only the value of the corresponding column. -/// -/// \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 CSVValue { public: - CSVValue() : isHeaderRead(false), Delimeter(','), - EndOfLine(','), Column(0), RowNumber(0) { } - - /// 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 readNextValue(std::istream &Str) { + void readNextRow(std::istream &Str) { std::string Line; std::getline(Str, Line); std::stringstream LineStream(Line); - - CSVRowParser::parseValue(LineStream, Data, Column, Delimeter); - - RowNumber = RowNumber + 1; - } - - bool isNumeric(const std::string& input){ - return std::all_of(input.begin(), input.end(), ::isdigit); - } - - void readHeaderRow(std::istream &Str){ - std::string Line; - std::getline(Str, Line); - std::stringstream LineStream(Line); - - CSVRowParser::parse(LineStream, Header, Delimeter); - - isHeaderRead = true; - - RowNumber = RowNumber + 1; - } - - - inline bool isHeaderAlreadyRead(){ - return isHeaderRead; - } - - inline HeaderInformation isHeaderSet(){ - return HeaderInfo; - } - - inline void setDelimeter(char Delimeter){ - this->Delimeter = Delimeter; - } - - inline char getDelimeter(){ - return this->Delimeter; + parseRow(LineStream, seq_t()); } - inline void setEndOfLine(char EndOfLine){ - this->EndOfLine = EndOfLine; - } - - inline char getEndOfLine(){ - return this->EndOfLine; - } - - inline bool isThisFirstRow(){ - return this->isFirstRow; - } - - inline void setColumn(const size_t &Column){ - this->Column = Column; - } - - inline void setHeaderInfo(const HeaderInformation HeaderInfo){ - this->HeaderInfo = HeaderInfo; - } - - inline void setSkipRows(const size_t &SkipRows){ - this->SkipRows = SkipRows; - } - - inline const size_t & getSkipRows(){ - return this->SkipRows; - } - - inline uint64_t getRowNumber(){ - return this->RowNumber; - } + /// Gives a constant references for the \c std::tuple containing the values + /// read by \p this object. + /// + /// \return \c CSVRow::Data + const std::tuple &tuple(void) const noexcept { return Data; } private: - std::vector Data; ///< Stores parsed entries - uint64_t RowNumber; ///< Current row number - std::vector Header; ///< Stores the header entries if available - char Delimeter; ///< Stores the delimeter between data entries - char EndOfLine; ///< Stores the end of line character - size_t Column; ///< Stores the column to get the data out of the row - HeaderInformation HeaderInfo; ///< Indicates if CSV file contains a header row (expected first row to be the header). - size_t SkipRows; ///< Number of Rows to skip at the beginning of the file. - bool isHeaderRead; ///< Indicates if header was read + std::tuple 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) { - size_t SkipRowsCorrected = 0; - - if (Data.isHeaderSet() == HeaderInformation::HasHeader && !Data.isHeaderAlreadyRead()){ - Data.readHeaderRow(Str); - } - - if (Data.isHeaderSet() == HeaderInformation::HasHeader){ - - } - - SkipRowsCorrected = Data.getSkipRows(); - if (Data.isHeaderSet() == HeaderInformation::HasHeader){ - SkipRowsCorrected = SkipRowsCorrected + 1; - } - - while (Data.getRowNumber() < SkipRowsCorrected){ - Data.readNextRow(Str); - } - - Data.readNextRow(Str); - /* // just for debugging purpose - char c; - while(Str.get(c)){ - std::cout << c; - } - std::cout << std::endl; - */ - return Str; -} - -/// Reads a value of CSV data into \c rosa::csv::CSVValue. -/// -/// The next line is read from \p Str by calling -/// \c rosa::csv::CSVValue::readNextValue on \p Data. -/// If the file contains a header, the first row and the second row -/// is read, so after the first read always valid data is available. -/// -/// \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, CSVValue &Data) { - size_t SkipRowsCorrected = 0; - - if (Data.isHeaderSet() == HeaderInformation::HasHeader && !Data.isHeaderAlreadyRead()){ - Data.readHeaderRow(Str); - } - - SkipRowsCorrected = Data.getSkipRows(); - if (Data.isHeaderSet() == HeaderInformation::HasHeader){ - SkipRowsCorrected = SkipRowsCorrected + 1; - } - - while (Data.getRowNumber() < SkipRowsCorrected){ - Data.readNextValue(Str); - } - - Data.readNextValue(Str); - /* // just for debugging purpose - char c; - while(Str.get(c)){ - std::cout << c; - } - std::cout << std::endl; - */ - return Str; -} - - - -/// Reads a value of CSV data into \c rosa::csv::CSVValue. -/// -/// The next line is read from \p Str by calling -/// \c rosa::csv::CSVValue::readNextValue on \p Data. -/// If the file contains a header, the first row and the second row -/// is read, so after the first read always valid data is available. +/// \tparam Ts type of values to read from the row /// -/// \note A CSV file should contain no empty lines. +/// \note The CSV file should contain a line with fields matching \p Ts... /// /// \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 +template std::istream &operator>>(std::istream &Str, CSVRow &Data) { - size_t SkipRowsCorrected = 0; - - Data.readNextRow(Str); - if (Data.isHeaderSet() == HeaderInformation::HasHeader && !Data.isHeaderAlreadyRead()){ - Data.readHeaderRow(Str); - } - - SkipRowsCorrected = Data.getSkipRows(); - if (Data.isHeaderSet() == HeaderInformation::HasHeader){ - SkipRowsCorrected = SkipRowsCorrected + 1; - } - - while (Data.getRowNumber() < SkipRowsCorrected){ - // Data.readNextValue(Str); - } - - //Data.readNextValue(Str); - /* // just for debugging purpose - char c; - while(Str.get(c)){ - std::cout << c; - } - std::cout << std::endl; - */ + Data.readNextRow(Str); return Str; } -} // End namespace +} // End namespace -/// Provides `InputIterator` features for iterating over a CSV file in a -/// flat way. +/// Provides `InputIterator` features for iterating over a CSV file. /// -/// The iterator hides rows of the CSV file, and iterates over the entries -/// row-by-row. +/// The iterator parses rows into `std::tuple` values and iterates over the +/// file row by row. /// -/// \note A CSV file should contain no empty lines. +/// \tparam Ts types of values stored in one row of the CSV file /// -/// \tparam T type of values to iterate over, i.e. entries in the CSV file. +/// \note The iterator expects each row to consists of fields matching \p Ts. /// /// \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 { +/// types -- and for \c std::string. Those are the valid values for \p Ts +template class CSVIterator { public: - /// \defgroup CSVFlatIteratorTypedefs Typedefs of rosa::csv::CSVFlatIterator + /// \defgroup CSVIteratorTypedefs Typedefs of rosa::csv::CSVIterator /// /// 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. + iterator_category; ///< Category of the iterator. + typedef std::tuple value_type; ///< Type of values iterated over. + typedef std::size_t difference_type; ///< Type to identify distance. + typedef std::tuple *pointer; ///< Pointer to the type iterated over. + typedef std::tuple + &reference; ///< Reference to the type iterated over. ///@} /// Creates a new instance. /// /// \param [in,out] S input stream to iterate over - CSVFlatIterator(std::istream &S, size_t Column = 0, HeaderInformation HeaderInfo = HeaderInformation::HasHeader, - bool MultipleRow = true, size_t SkipRows = 0, const char Delimeter = ',', const char EndOfLine = '\n') - : Str(S.good() ? &S : nullptr), - Pos(static_cast(-1)), Column(Column), HeaderInfo(HeaderInfo), - MultipleRow(MultipleRow), SkipRows(SkipRows), - Delimeter(Delimeter), EndOfLine(EndOfLine) - { - Row.setHeaderInfo(HeaderInfo); - Row.setSkipRows(SkipRows); - Row.setDelimeter(Delimeter); - Row.setEndOfLine(EndOfLine); - Row.setColumn(Column); - - Value.setHeaderInfo(HeaderInfo); - Value.setSkipRows(SkipRows); - Value.setDelimeter(Delimeter); - Value.setEndOfLine(EndOfLine); - Value.setColumn(Column); - // \c rosa::csv::CSVFlatIterator::Pos is initialized to `-1` so the first - // incrementation here will set it properly. + CSVIterator(std::istream &S) : Str(S.good() ? &S : nullptr), Row() { + // \c rosa::csv::CSVIterator::Row is initialized empty so the first + // incrementation here will read the first row. ++(*this); } /// Creates an empty new instance. - CSVFlatIterator(void) noexcept : Str(nullptr), MultipleRow(true) {} + CSVIterator(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. + /// The implementation reads the next row. 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++() { + CSVIterator &operator++() { if (Str) { - ++Pos; - if(MultipleRow){ - if (Pos == Row.size()) { - if (!((*Str) >> Row)) { - Str = nullptr; - --Pos; // Stay on the last entry forever. - } else { - Pos = 0; - } - } - }else{ - if (Pos == Value.size()) { - if (!((*Str) >> Value)) { - Str = nullptr; - --Pos; // Stay on the last entry forever. - } 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 constanStartt reference to the current entry. - const T &operator*(void)const noexcept { - if(MultipleRow){ - return Row[Pos]; - }else { - return Value[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 { - if(MultipleRow){ - return &Row[Pos]; - }else { - return &Value[Pos]; + if (!((*Str) >> Row)) { + Str = nullptr; } - } - - /// 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); - } - - inline void setDelimeter(char Delimter){ - this->Delimeter = Delimter; - } - - inline char getDelimeter(){ - return this->Delimeter; - } - -private: - std::istream *Str; ///< Input stream of a CSV file to iterate over. - CSVRow Row; ///< Content of the current row iterating over. - CSVValue Value; ///< Content of the specified column in the current row. - size_t Pos; ///< Current position within the current row. - char Delimeter; ///< Delimeter between the entries - char EndOfLine; ///< stores the end of line character - size_t Column; ///< Index of the column to get data out of it, starts at zero. - HeaderInformation HeaderInfo; ///< Indicates if CSV file contains a header row (expected first row to be the header). - bool MultipleRow; ///< Indicates if you want to read a row or only one column out of a row. true = use CSVRow - size_t SkipRows; ///< Number of Rows to skip at the beginning of the file. - -}; - - -/// 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 CSVTupleIterator { -public: - /// \defgroup CSVTupleIteratorTypedefs Typedefs of rosa::csv::CSVTupleIterator - /// - /// Standard `typedef`s for iterators. - /// - ///@{ - typedef std::input_iterator_tag - iterator_category; ///< Category of the iterator. - typedef std::tuple value_type; ///< Type of values iterated over. - typedef std::size_t difference_type; ///< Type to identify distance. - typedef std::tuple *pointer; ///< Pointer to the type iterated over. - typedef std::tuple &reference; ///< Reference to the type iterated over. - ///@} - - - /// Creates a new instance. - /// - /// \param [in,out] S input stream to iterate over - - - CSVTupleIterator(std::istream &S, size_t Column = 0, HeaderInformation HeaderInfo = HeaderInformation::HasHeader, - bool MultipleRow = true, size_t SkipRows = 0, const char Delimeter = ',', const char EndOfLine = '\n') - : Str(S.good() ? &S : nullptr), - Pos(static_cast(-1)), Column(Column), HeaderInfo(HeaderInfo), - MultipleRow(MultipleRow), SkipRows(SkipRows), - Delimeter(Delimeter), EndOfLine(EndOfLine) - { - Row.setHeaderInfo(HeaderInfo); - Row.setSkipRows(SkipRows); - Row.setDelimeter(Delimeter); - Row.setEndOfLine(EndOfLine); - Row.setColumn(Column); - - /*Value.setHeaderInfo(HeaderInfo); - Value.setSkipRows(SkipRows); - Value.setDelimeter(Delimeter); - Value.setEndOfLine(EndOfLine); - Value.setColumn(Column);*/ - // \c rosa::csv::CSVFlatIterator::Pos is initialized to `-1` so the first - // incrementation here will set it properly. - ++(*this); - } - - - - /// Creates an empty new instance. - CSVTupleIterator(void) noexcept : Str(nullptr), MultipleRow(true) {} - - /// 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. - CSVTupleIterator &operator++() { - if (Str) { - ++Pos; - if (!((*Str) >> Row)){ - Str = nullptr; - --Pos; - }else { - Pos = 0; - } - - /* - if(MultipleRow){ - //if (Pos == Row.size()) { - if (!((*Str) >> Row)) { - Str = nullptr; - --Pos; // Stay on the last entry forever. - } else { - Pos = 0; - } - // } - }else{ - if (Pos == Value.size()) { - if (!((*Str) >> Value)) { - Str = nullptr; - --Pos; // Stay on the last entry forever. - } 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. - CSVTupleIterator operator++(int) { - CSVTupleIterator Tmp(*this); + CSVIterator operator++(int) { + CSVIterator Tmp(*this); ++(*this); return Tmp; } /// Returns a constant reference to the current entry. /// /// \note Should not dereference the iterator when it is empty. /// - /// \return constanStartt reference to the current entry. - const std::tuple &operator*(void)const noexcept { - if(MultipleRow){ - return Row.tuple(); - }else { - //return Value.tuple(); - } - } + /// \return constant reference to the current entry. + const std::tuple &operator*(void)const noexcept { return Row.tuple(); } /// 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 std::tuple *operator->(void)const noexcept { - if(MultipleRow){ - return &Row.tuple(); - }else { - //return &Value.tuple(); - } + const std::tuple *operator->(void)const noexcept { + return &Row.tuple(); } /// 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 CSVTupleIterator &RHS) const noexcept { + bool operator==(const CSVIterator &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 CSVTupleIterator &RHS) const noexcept { + bool operator!=(const CSVIterator &RHS) const noexcept { return !((*this) == RHS); } - inline void setDelimeter(char Delimter){ - this->Delimeter = Delimter; - } - - inline char getDelimeter(){ - return this->Delimeter; - } - private: - std::istream *Str; ///< Input stream of a CSV file to iterate over. - CSVRow Row; ///< Content of the current row iterating over. - //CSVValue Value; ///< Content of the specified column in the current row. - size_t Pos; ///< Current position within the current row. - char Delimeter; ///< Delimeter between the entries - char EndOfLine; ///< stores the end of line character - size_t Column; ///< Index of the column to get data out of it, starts at zero. - HeaderInformation HeaderInfo; ///< Indicates if CSV file contains a header row (expected first row to be the header). - bool MultipleRow; ///< Indicates if you want to read a row or only one column out of a row. true = use CSVRow - size_t SkipRows; ///< Number of Rows to skip at the beginning of the file. - + std::istream *Str; ///< Input stream of a CSV file to iterate over. + CSVRow Row; ///< Content of the current row. }; } // End namespace csv } // End namespace rosa #endif // ROSA_SUPPORT_CSV_CSVREADER_HPP