diff --git a/.vimrc b/.vimrc new file mode 100644 index 0000000..b7549e3 --- /dev/null +++ b/.vimrc @@ -0,0 +1,34 @@ +function! Build() + silent :!clear + silent :!mkdir -p build + silent :!cd build && cmake .. && make + :!echo "Done" + redraw! +endfunction +command! Build :call Build() + +function! Clean() + silent :!clear + silent :!cd build && rm -frv * + :!echo "Done" + redraw! +endfunction +command! Clean :call Clean() + +function! Run() + silent :!clear + silent :!cd build && ./project + :!echo "Done" + redraw! +endfunction +command! Run :call Run() + +function! Test() + silent :!clear + silent :!cd build/test && ./ProjectTest; + silent :!cd build/test && ./CalculatorTest; + silent :!cd build/test && ./ParserTest; + :!echo "Done" + redraw! +endfunction +command! Test :call Test() diff --git a/CMakeLists.txt b/CMakeLists.txt index e6318a3..16cdba8 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("Calculator") +include_directories("Parser") +include_directories("types") +set(SOURCES project.cc Calculator/Calculator.cc Parser/Parser.cc) add_library(ProjectLib ${SOURCES}) add_executable(project main.cc) diff --git a/Calculator/Calculator.cc b/Calculator/Calculator.cc new file mode 100644 index 0000000..05b592e --- /dev/null +++ b/Calculator/Calculator.cc @@ -0,0 +1,46 @@ +#include "Calculator.h" +#include + +namespace dev { + Calculator::Calculator(types::NumbersStackPtr numbers, + types::OperatorsListPtr operators) + : numbers_(std::move(numbers)) + , operators_(std::move(operators)) { } + +float Calculator::calculate() { + + if (operators_->size() != numbers_->size() - 1) { + return types::kError; + } + + for(const auto& math_operator : *operators_) { + auto left = numbers_->pop(); + auto right = numbers_->pop(); + switch(*math_operator.begin()) { + case types::Operators::ADD: numbers_->push(add(left,right)); break; + case types::Operators::DEC: numbers_->push(dec(left,right)); break; + case types::Operators::DIV: numbers_->push(div(left,right)); break; + case types::Operators::MUL: numbers_->push(mul(left,right)); break; + default:break; + } + } + return numbers_->top(); +} + +float Calculator::add(float first, float second) { + return first + second; +} + +float Calculator::div(float first, float second) { + return second ? (first / second) : types::kError; +} + +float Calculator::dec(float first, float second) { + return first - second; +} + +float Calculator::mul(float first, float second) { + return first * second; +} + +} diff --git a/Calculator/Calculator.h b/Calculator/Calculator.h new file mode 100644 index 0000000..6a99b27 --- /dev/null +++ b/Calculator/Calculator.h @@ -0,0 +1,31 @@ +#ifndef CALCULATOR_CALCULATOR_H +#define CALCULATOR_CALCULATOR_H + +#include "ICalculator.h" +#include "CustomStack.h" +#include "utils.h" + +#include +#include +#include +#include + +namespace dev { + + class Calculator : public ICalculator { + public: + Calculator(types::NumbersStackPtr numbers, types::OperatorsListPtr operators); + float calculate() override; + + private: + float add(float first, float second); + float div(float first, float second); + float dec(float first, float second); + float mul(float first, float second); + + types::NumbersStackPtr numbers_; + types::OperatorsListPtr operators_; + }; +} + +#endif diff --git a/Calculator/ICalculator.h b/Calculator/ICalculator.h new file mode 100644 index 0000000..2ba5280 --- /dev/null +++ b/Calculator/ICalculator.h @@ -0,0 +1,12 @@ +#ifndef CALCULATOR_ICALCULATOR_H +#define CALCULATOR_ICALCULATOR_H + +namespace dev { + +class ICalculator { + public: + virtual float calculate() = 0; +}; + +} +#endif diff --git a/Parser/IParser.h b/Parser/IParser.h new file mode 100644 index 0000000..75b840f --- /dev/null +++ b/Parser/IParser.h @@ -0,0 +1,14 @@ +#ifndef PARSER_IPARSER_H +#define PARSER_IPARSER_H + +#include + +namespace dev { + +class IParser { + public: + virtual bool parse(const std::string& data) = 0; +}; + +} +#endif diff --git a/Parser/Parser.cc b/Parser/Parser.cc new file mode 100644 index 0000000..2ccc955 --- /dev/null +++ b/Parser/Parser.cc @@ -0,0 +1,70 @@ +#include "Parser.h" + +namespace dev { +Parser::Parser() + : numbers_(new types::NumbersStack()), + operators_(new types::OperatorsList()) { +} + +bool Parser::parse(const std::string& data) { + if (data.empty()) { + return false; + } + + const auto& splitted_data = split(data); + for (const auto& user_data : splitted_data) { + + // Append operators list + std::string math_operator = string_matches_operator(user_data); + if (!math_operator.empty()) { + operators_->push_back(math_operator); + continue; + } + + // Append numbers list + try { + float number = std::stof(user_data); + numbers_->push(number); + } catch (const std::exception& exception) { + std::cerr << "cannot convert "<< user_data << " to number!" << std::endl; + } + } + + if (operators_->size() != numbers_->size() - 1) { + return false; + } + + return true; +} + +std::string +Parser::string_matches_operator(const std::string& possible_operator) const { + if (possible_operator != "*" && + possible_operator != "+" && + possible_operator != "-" && + possible_operator != "/") { + return ""; + } + + return possible_operator; +} + +std::vector Parser::split(const std::string& data) const { + std::istringstream data_stream(data); + std::string buffer; + std::vector result; + while (std::getline(data_stream, buffer, ' ')) { + result.push_back(buffer); + } + return result; +} + +types::NumbersStackPtr Parser::get_numbers() { + return std::move(numbers_); +} + +types::OperatorsListPtr Parser::get_operators() { + return std::move(operators_); +} + +} diff --git a/Parser/Parser.h b/Parser/Parser.h new file mode 100644 index 0000000..3c4937c --- /dev/null +++ b/Parser/Parser.h @@ -0,0 +1,38 @@ +#ifndef PARSER_PARSER_H +#define PARSER_PARSER_H + +#include "IParser.h" +#include "CustomStack.h" +#include "utils.h" + +#include +#include +#include +#include +#include +#include +#include + +namespace dev { + + using namespace types; + + class Parser : public IParser { + public: + Parser(); + bool parse(const std::string& data) override; + + types::NumbersStackPtr get_numbers(); + types::OperatorsListPtr get_operators(); + + private: + std::string + string_matches_operator(const std::string& possible_operator) const; + std::vector + split(const std::string&) const; + types::NumbersStackPtr numbers_; + types::OperatorsListPtr operators_; + }; +} + +#endif diff --git a/iproject.h b/iproject.h index ec4452f..a6f7ecc 100644 --- a/iproject.h +++ b/iproject.h @@ -1,9 +1,10 @@ #pragma once +#include namespace dev{ class IProject { - virtual int run() = 0; + virtual float run(const std::string& user_input) = 0; }; } diff --git a/main.cc b/main.cc index 5b2357f..70cb1dd 100644 --- a/main.cc +++ b/main.cc @@ -1,5 +1,30 @@ #include "project.h" +#include + + +namespace { + const int kUserInputLimit = 1000; +} + +//TODO: Maybe add IO handler ?? +std::string handle_input() { + char user_input[kUserInputLimit]; + std::cout << "Input expression -> "; + std::cin.getline(user_input, sizeof(user_input)); + return std::string(user_input); +} + +void handle_output(float result) { + if (result == std::numeric_limits::min()) { + std::cout << "Error occured while calculating!" << std::endl; + } else { + std::cout << (float)result << std::endl; + } +} int main() { + std::unique_ptr polish_calculator(new dev::Project); + float result = polish_calculator->run(handle_input()); + handle_output(result); return 0; } diff --git a/project.cc b/project.cc index fdc1225..af8a566 100644 --- a/project.cc +++ b/project.cc @@ -2,7 +2,23 @@ namespace dev { -int Project::run() { - return 0; +namespace { + const auto kError = std::numeric_limits::min(); } + +Project::Project() { +} + +float Project::run(const std::string& user_input) { + parser_.reset(new Parser()); + const bool parsing_successful = parser_->parse(user_input); + if (!parsing_successful) { + return kError; + } + + calculator_.reset(new Calculator(parser_->get_numbers(), + parser_->get_operators())); + return calculator_->calculate(); +} + } // namespace dev diff --git a/project.h b/project.h index b5cc813..138096c 100644 --- a/project.h +++ b/project.h @@ -1,11 +1,25 @@ #pragma once #include "iproject.h" +#include "Calculator/Calculator.h" +#include "Parser/Parser.h" +#include +#include +#include +#include +#include +#include +#include +#include "CustomStack.h" namespace dev { class Project : public IProject { - // IProject interface public: - int run(); + Project(); + float run(const std::string& user_input); + + private: + std::unique_ptr calculator_; + std::unique_ptr parser_; }; } // namespace dev diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 8d6190d..90aea53 100755 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -3,5 +3,11 @@ include_directories (${CMAKE_SOURCE_DIR}) include(AddGoogleTest) add_executable (ProjectTest project_test.cc) +add_executable (CalculatorTest calculator_test.cc) +add_executable (ParserTest parser_test.cc) add_gtest(ProjectTest) +add_gtest(CalculatorTest) +add_gtest(ParserTest) target_link_libraries(ProjectTest PUBLIC ProjectLib) +target_link_libraries(CalculatorTest PUBLIC ProjectLib) +target_link_libraries(ParserTest PUBLIC ProjectLib) diff --git a/test/calculator_test.cc b/test/calculator_test.cc new file mode 100644 index 0000000..eb01558 --- /dev/null +++ b/test/calculator_test.cc @@ -0,0 +1,67 @@ +#include "Calculator.h" +#include +#include +namespace dev { +namespace testing { + +class CalculatorTest : public ::testing::Test { + public: + void SetUp() override { + } + void TearDown() override { + numbers_.reset(); + operators_.reset(); + } + + void PrepareTest(const std::initializer_list& numbers, + const std::initializer_list& operators) { + numbers_.reset(new types::NumbersStack(numbers)); + operators_.reset(new types::OperatorsList(operators)); + calculator_.reset(new Calculator(std::move(numbers_), + std::move(operators_))); + } + std::unique_ptr calculator_; + types::NumbersStackPtr numbers_; + types::OperatorsListPtr operators_; +}; + +TEST_F(CalculatorTest, CalculateAdd) { + PrepareTest({10.0,20.0},{"+"}); + EXPECT_EQ(30.0,calculator_->calculate()); +} +TEST_F(CalculatorTest, CalculateDecrease) { + PrepareTest({10.0,20.0},{"-"}); + EXPECT_EQ(10.0,calculator_->calculate()); +} +TEST_F(CalculatorTest, CalculateMultiply) { + PrepareTest({10.0,20.0},{"*"}); + EXPECT_EQ(200.0,calculator_->calculate()); +} +TEST_F(CalculatorTest, CalculateDiv) { + PrepareTest({10.0,20.0},{"/"}); + EXPECT_EQ(2.0,calculator_->calculate()); +} +TEST_F(CalculatorTest, CalculateComplex) { + PrepareTest({10.0,20.0,30.0,40.0,80.0},{"+","*","-","/"}); + EXPECT_EQ(358.0,calculator_->calculate()); +} + +TEST_F(CalculatorTest, CalculateNoNumbers) { + PrepareTest({},{"+","*","-","/"}); + EXPECT_EQ(types::kError,calculator_->calculate()); +} +TEST_F(CalculatorTest, CalculateNoOperators) { + PrepareTest({10.0,20.0},{}); + EXPECT_EQ(types::kError,calculator_->calculate()); +} +TEST_F(CalculatorTest, CalculateNothing) { + PrepareTest({},{}); + EXPECT_EQ(types::kError,calculator_->calculate()); +} +TEST_F(CalculatorTest, CalculateDivideZero) { + PrepareTest({0.0,1.0},{"/"}); + EXPECT_EQ(types::kError,calculator_->calculate()); +} + +} // namespace testing +} // namespace dev diff --git a/test/parser_test.cc b/test/parser_test.cc new file mode 100644 index 0000000..4fa378c --- /dev/null +++ b/test/parser_test.cc @@ -0,0 +1,43 @@ +#include "Parser.h" +#include +#include + +namespace dev { +namespace testing { + +class ParserTest : public ::testing::Test { + protected: + dev::Parser parser_; +}; + +TEST_F(ParserTest, ParseString_Add) { + EXPECT_TRUE(parser_.parse("5 5 +")); +} +TEST_F(ParserTest, ParseString_Dec) { + EXPECT_TRUE(parser_.parse("5 5 -")); +} +TEST_F(ParserTest, ParseString_Div) { + EXPECT_TRUE(parser_.parse("5 5 /")); +} +TEST_F(ParserTest, ParseString_Mul) { + EXPECT_TRUE(parser_.parse("5 5 *")); +} +TEST_F(ParserTest, ParseString_Complex) { + EXPECT_TRUE(parser_.parse("5 5 4 3 * - +")); +} + +TEST_F(ParserTest, ParseString_ComplexFAIL) { + EXPECT_FALSE(parser_.parse("5 5 4 3 * - + *")); +} +TEST_F(ParserTest, ParseString_ParseNothing) { + EXPECT_FALSE(parser_.parse("")); +} +TEST_F(ParserTest, ParseString_ParseNumbersOnly) { + EXPECT_FALSE(parser_.parse(" 5 4 3 2 ")); +} +TEST_F(ParserTest, ParseString_ParseOperatorsOnly) { + EXPECT_FALSE(parser_.parse(" + - = * ")); +} + +} // namespace testing +} // namespace dev diff --git a/test/project_test.cc b/test/project_test.cc index 5dbeb2f..662e7b4 100644 --- a/test/project_test.cc +++ b/test/project_test.cc @@ -4,6 +4,10 @@ namespace dev { namespace testing { +namespace { + const auto kError = std::numeric_limits::min(); +} + class ProjectTest : public ::testing::Test { public: void SetUp() override {} @@ -11,8 +15,58 @@ class ProjectTest : public ::testing::Test { dev::Project project_; }; -TEST_F(ProjectTest, Run) { - ASSERT_EQ(0, project_.run()); +TEST_F(ProjectTest, ParseString_FAILURE) { + EXPECT_EQ(kError, project_.run("")); + EXPECT_EQ(kError, project_.run(" 5 4 3 2 ")); + EXPECT_EQ(kError, project_.run(" + - = * ")); +} + +TEST_F(ProjectTest, ParseString_SUCCESS) { + EXPECT_TRUE(project_.run("5 5 +")); +} + +TEST_F(ProjectTest, CalculateNothing) { + EXPECT_EQ(kError, project_.run("")); +} + +TEST_F(ProjectTest, CalculateNoOperators) { + EXPECT_EQ(kError, project_.run("5 5")); +} + +TEST_F(ProjectTest, CalculateNoNumbers) { + EXPECT_EQ(kError, project_.run("+ - / *")); +} + +TEST_F(ProjectTest, CalculateAdd) { + EXPECT_EQ(10, project_.run("5 5 +")); +} + +TEST_F(ProjectTest, CalculateDecrease) { + EXPECT_EQ(-2, project_.run("7 5 -")); +} + +TEST_F(ProjectTest, CalculateMultiply) { + EXPECT_EQ(35, project_.run("5 7 *")); +} + +TEST_F(ProjectTest, CalculateDivide) { + EXPECT_EQ(2, project_.run("7 14 /")); +} + +TEST_F(ProjectTest, CalculateAddMultiple) { + EXPECT_EQ(28, project_.run("14 7 3 4 + + +")); +} + +TEST_F(ProjectTest, CalculateSeveralOperations) { + EXPECT_EQ(28, project_.run("4 4 4 4 + * -")); +} + +TEST_F(ProjectTest, CalculateSeveralOperationsDifferentPlacementOrder) { + EXPECT_EQ(15, project_.run("1 2 + 4 * 3 +")); +} + +TEST_F(ProjectTest, CalculateDividingZero) { + EXPECT_EQ(kError, project_.run("0 1 /")); } } // namespace testing diff --git a/types/CustomStack.h b/types/CustomStack.h new file mode 100644 index 0000000..026c8c1 --- /dev/null +++ b/types/CustomStack.h @@ -0,0 +1,59 @@ +#ifndef TYPES_CUSTOMSTACK_H +#define TYPES_CUSTOMSTACK_H + +#include +#include +#include +#include + +namespace types { + + template + class CustomStack { + public: + explicit CustomStack() = default; + explicit CustomStack(const CustomStack& ) = default; + explicit CustomStack(CustomStack&&) = default; + explicit CustomStack(const std::initializer_list& list) { + for (const auto& element : list) { + stack_.push(element); + } + } + + void push(T value) { + stack_.push(value); + } + + T pop() { + T top_element = stack_.top(); + stack_.pop(); + return top_element; + } + + size_t size() const { + return stack_.size(); + } + + std::stack get_stack() const { + return stack_; + } + + T top() const { + return stack_.top(); + } + + bool empty() const { + return stack_.empty(); + } + + void clear() { + while(!empty()) { + stack_.pop(); + } + } + + private: + std::stack stack_; + }; +} +#endif diff --git a/types/utils.h b/types/utils.h new file mode 100644 index 0000000..2c77565 --- /dev/null +++ b/types/utils.h @@ -0,0 +1,24 @@ +#ifndef UTILS_H +#define UTILS_H + +#include "CustomStack.h" +#include + +namespace types { + + typedef std::unique_ptr> NumbersStackPtr; + typedef CustomStack NumbersStack; + typedef std::unique_ptr> OperatorsListPtr; + typedef std::vector OperatorsList; + + const auto kError = std::numeric_limits::min(); + + enum Operators : char { + ADD = '+', + DEC = '-', + DIV = '/', + MUL = '*' + }; +} + +#endif