diff --git a/CMakeLists.txt b/CMakeLists.txt index e6318a3..dbe2613 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -4,7 +4,10 @@ project(dummy_cmake_project) set(CMAKE_CXX_STANDARD 14) list(APPEND CMAKE_MODULE_PATH ${PROJECT_SOURCE_DIR}/cmake) -set(SOURCES project.cc) +include_directories(${CMAKE_CURRENT_SOURCE_DIR}/include) + +file(GLOB_RECURSE SOURCES ${CMAKE_CURRENT_SOURCE_DIR}/src/*.cc) + add_library(ProjectLib ${SOURCES}) add_executable(project main.cc) diff --git a/include/fixed_queue.h b/include/fixed_queue.h new file mode 100644 index 0000000..e8b3344 --- /dev/null +++ b/include/fixed_queue.h @@ -0,0 +1,11 @@ +#include + +namespace dev { +class FixedSizeQueue : public std::queue { +public: + void push(const float& item); + bool getFirst(float& item); +private: + static constexpr int _size = 2; +}; +} diff --git a/include/iproject.h b/include/iproject.h new file mode 100644 index 0000000..794a16c --- /dev/null +++ b/include/iproject.h @@ -0,0 +1,10 @@ +#pragma once +#include "string" + +namespace dev{ + +class IProject { + virtual float run(const std::string& expression) = 0; +}; + +} diff --git a/include/project.h b/include/project.h new file mode 100644 index 0000000..502e3c4 --- /dev/null +++ b/include/project.h @@ -0,0 +1,19 @@ +#pragma once +#include "iproject.h" +#include "fixed_queue.h" + +#include +#include +#include + +namespace dev { + +class Project : public IProject { + // IProject interface + public: + float run(const std::string& expression = ""); + + private: + FixedSizeQueue _queue; +}; +} // namespace dev diff --git a/include/string_generator.h b/include/string_generator.h new file mode 100644 index 0000000..9f349f0 --- /dev/null +++ b/include/string_generator.h @@ -0,0 +1,19 @@ +namespace dev { +class StringGenerator{ +public: + StringGenerator(const std::string& basic); + bool next(std::string& result); + bool empty(); + + static bool isOperation(const std::string& item); + static bool isOperand(const std::string& item); + static bool getOperand(const std::string& item, float& result); + + static constexpr char cPlus = '+'; + static constexpr char cMinus = '-'; + static constexpr char cMult = '*'; + static constexpr char cDiv = '/'; +private: + std::string _basic; +}; +} diff --git a/iproject.h b/iproject.h deleted file mode 100644 index ec4452f..0000000 --- a/iproject.h +++ /dev/null @@ -1,9 +0,0 @@ -#pragma once - -namespace dev{ - -class IProject { - virtual int run() = 0; -}; - -} diff --git a/main.cc b/main.cc index 5b2357f..c7d9b95 100644 --- a/main.cc +++ b/main.cc @@ -1,5 +1,20 @@ #include "project.h" +#include -int main() { - return 0; +int main(int argc, char *argv[]) { + //TODO: + // There should be easier way to convert CL args to a single string + std::string args= ""; + for (int i = 1; i < argc; ++i) { + args += argv[i]; + if (i != argc-1) { + args += ' '; + } + } + + dev::Project project; + float result = project.run(args); + std::cout << "Result: " << result << std::endl; + + return 0; } diff --git a/project.cc b/project.cc deleted file mode 100644 index fdc1225..0000000 --- a/project.cc +++ /dev/null @@ -1,8 +0,0 @@ -#include "project.h" - -namespace dev { - -int Project::run() { - return 0; -} -} // namespace dev diff --git a/project.h b/project.h deleted file mode 100644 index b5cc813..0000000 --- a/project.h +++ /dev/null @@ -1,11 +0,0 @@ -#pragma once -#include "iproject.h" - -namespace dev { - -class Project : public IProject { - // IProject interface - public: - int run(); -}; -} // namespace dev diff --git a/src/fixed_queue.cc b/src/fixed_queue.cc new file mode 100644 index 0000000..edf2481 --- /dev/null +++ b/src/fixed_queue.cc @@ -0,0 +1,21 @@ +#include "fixed_queue.h" + +namespace dev { + void FixedSizeQueue::push(const float& item){ + while (_size <= this->size()){ + this->pop(); + } + std::queue::push(item); + } + + // Not obvious naming + bool FixedSizeQueue::getFirst(float& result) { + result = 0; // result will be changed it bad case (if stack is empty) + if (this->empty()){ + return false; + } + result = this->front(); + this->pop(); + return true; + } +} diff --git a/src/project.cc b/src/project.cc new file mode 100644 index 0000000..401ccec --- /dev/null +++ b/src/project.cc @@ -0,0 +1,64 @@ +#include "project.h" +#include "string_generator.h" + +namespace dev { + +float Project::run(const std::string& expression) { + auto stuff = [this](const std::string& operation, float& result) { + float operand1; + float operand2; + + if (!_queue.getFirst(operand1) || //getFirst change stack. but name of function does not say about it + ! _queue.getFirst(operand2)) { + // UNDEFINED!!! + throw std::runtime_error("Single operand! Undefined behaviour!"); + } + + const char& cOperation = operation.front(); + switch (cOperation) { + case StringGenerator::cPlus: + result = operand1 + operand2; + break; + case StringGenerator::cMinus: + result = operand1 - operand2; + break; + case StringGenerator::cMult: + result = operand1 * operand2; + break; + case StringGenerator::cDiv: + result = operand1 / operand2; // I can write 1 / 0 + break; + default: + // UNREACHABLE!!! + throw std::runtime_error("Undefined operation!"); + } + return true; + }; + + auto sGenerator = StringGenerator(expression); + + // Think of case, when this result will be returned in case of wrong line + float result = 0; + std::string item; + while(sGenerator.next(item)) { + // Not obvious loop + if (StringGenerator::isOperation(item)) { + stuff(item, result); + _queue.push(result); + continue; + } + + if (StringGenerator::getOperand(item, result)) { + _queue.push(result); + continue; + } + + // UNREACHABLE!!!" + throw std::runtime_error("Unreachable!"); + } + + + _queue.getFirst(result); + return result; +} +} // namespace dev diff --git a/src/string_generator.cc b/src/string_generator.cc new file mode 100644 index 0000000..5bf6b08 --- /dev/null +++ b/src/string_generator.cc @@ -0,0 +1,66 @@ +#include +#include +#include "string_generator.h" + +namespace dev { + StringGenerator::StringGenerator(const std::string& basic) + : _basic(basic) {} + + bool StringGenerator::empty() { + return _basic.empty(); + } + + bool StringGenerator::next(std::string& result) { + result = ""; + //not obvious loop! + while(!empty()) { + + // get front and pop!!! + auto item = _basic.front(); + _basic.erase(0,1); + + // how about const? + if (' ' == item) { + break; + } + result += item; + } + return !result.empty(); + } + + bool StringGenerator::isOperation(const std::string& item) { + if (item.length() > 1) { + return false; + } + + const auto& front = item.front(); + + switch (front) { + case StringGenerator::cPlus: + case StringGenerator::cMinus: + case StringGenerator::cDiv: + case StringGenerator::cMult: + return true; + default: + return false; + } + } + + bool StringGenerator::isOperand(const std::string& item) { + float result; + return getOperand(item, result); + } + + bool StringGenerator::getOperand(const std::string& item, float& result) { + if (item.empty()) { + return false; + } + + // TODO: + // I bet this part could be simplified... + std::istringstream iss(item); + iss >> std::noskipws >> result; // noskipws considers leading whitespace invalid + // Check the entire string was consumed and if either failbit or badbit is set + return iss.eof() && !iss.fail(); + } +} diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 8d6190d..1e38328 100755 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -3,5 +3,10 @@ include_directories (${CMAKE_SOURCE_DIR}) include(AddGoogleTest) add_executable (ProjectTest project_test.cc) +add_executable (FixedQueueTest fixed_size_queue_test.cc) + add_gtest(ProjectTest) +add_gtest(FixedQueueTest) + target_link_libraries(ProjectTest PUBLIC ProjectLib) +target_link_libraries(FixedQueueTest PUBLIC ProjectLib) diff --git a/test/fixed_size_queue_test.cc b/test/fixed_size_queue_test.cc new file mode 100644 index 0000000..a50b060 --- /dev/null +++ b/test/fixed_size_queue_test.cc @@ -0,0 +1,52 @@ + +#include "fixed_queue.h" +#include +#include + +namespace dev { +namespace testing { + +class FixedSizeQueueTest : public ::testing::Test { + public: + void SetUp() override { + FixedSizeQueue tmp_queue; + std::swap(queue_, tmp_queue); + } + void TearDown() override {} + + FixedSizeQueue queue_; +}; + +TEST_F(FixedSizeQueueTest, Push) { + float item = 5.1; + EXPECT_NO_THROW(queue_.push(item)); +} + +TEST_F(FixedSizeQueueTest, GetFirst) { + float item_1 = 5.1; + float item_2 = 3.2; + queue_.push(item_1); + queue_.push(item_2); + + float result = 0; + ASSERT_TRUE(queue_.getFirst(result)); + + EXPECT_EQ(result, item_1); +} + +TEST_F(FixedSizeQueueTest, PushThreeItems) { + float item_1 = 5.1; + float item_2 = 3.2; + float item_3 = 1.1; + queue_.push(item_1); + queue_.push(item_2); + queue_.push(item_3); + + float result = 0; + ASSERT_TRUE(queue_.getFirst(result)); + + EXPECT_EQ(result, item_2); +} + +} // namespace testing +} // namespace dev diff --git a/test/project_test.cc b/test/project_test.cc index 5dbeb2f..444daf9 100644 --- a/test/project_test.cc +++ b/test/project_test.cc @@ -11,9 +11,40 @@ class ProjectTest : public ::testing::Test { dev::Project project_; }; -TEST_F(ProjectTest, Run) { +TEST_F(ProjectTest, Run_Basic) { ASSERT_EQ(0, project_.run()); + ASSERT_EQ(10, project_.run("10")); + ASSERT_EQ(10, project_.run("10 4")); } +TEST_F(ProjectTest, Simple_Operations) { + ASSERT_EQ(15, project_.run("10 5 +")); + ASSERT_EQ(5, project_.run("10 5 -")); + ASSERT_EQ(50, project_.run("10 5 *")); + ASSERT_EQ(2, project_.run("10 5 /")); +} + +TEST_F(ProjectTest, Simple_Operations_Float) { + ASSERT_NEAR(15.2, project_.run("10.1 5.1 +"), 0.01); + ASSERT_NEAR(5, project_.run("10.1 5.1 -"), 0.01); + ASSERT_NEAR(51.51, project_.run("10.1 5.1 *"), 0.01); + ASSERT_NEAR(2.02, project_.run("10.1 5 /"), 0.01); +} + +TEST_F(ProjectTest, Simple_Operations_Exceptions) { + ASSERT_THROW(project_.run("a"), std::runtime_error); + ASSERT_THROW(project_.run("a a"), std::runtime_error); + ASSERT_THROW(project_.run("+"), std::runtime_error); + ASSERT_THROW(project_.run("10 +"), std::runtime_error); +} + +TEST_F(ProjectTest, Simple_Operations_Complex) { + EXPECT_EQ(17, project_.run("10 5 + 2 +")); + EXPECT_EQ(7, project_.run("10 5 2 +")); +} + +//TODO: +// Increase coverage + } // namespace testing } // namespace dev