diff --git a/CMakeLists.txt b/CMakeLists.txt index e6318a3..04cf393 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -4,10 +4,10 @@ project(dummy_cmake_project) set(CMAKE_CXX_STANDARD 14) list(APPEND CMAKE_MODULE_PATH ${PROJECT_SOURCE_DIR}/cmake) -set(SOURCES project.cc) +set(SOURCES project.cc Parser.cpp Token.cpp Stack.cpp Calculator.cpp executor.cpp) add_library(ProjectLib ${SOURCES}) -add_executable(project main.cc) +add_executable(project main.cc Parser.cpp Token.cpp Stack.cpp Calculator.cpp executor.cpp) target_link_libraries(project ProjectLib) enable_testing() diff --git a/Calculator.cpp b/Calculator.cpp new file mode 100644 index 0000000..9246e69 --- /dev/null +++ b/Calculator.cpp @@ -0,0 +1,49 @@ +#include "Calculator.h" +#include + +Calculator::Calculator(Stack &stk) : stk_(stk) +{} + +double Calculator::exe() { + + std::vector numbers; + double rec = 0; + for(;;) { + int rc; + Token t = stk_.pop(rc); + if(t.is_number()) { + numbers.push_back(t.getNumber()); + } else { //operation + + long double result = numbers[0]; + for(int i = 1; i < numbers.size(); ++i) { + + switch (t.getOperand()) { + case TOper::ADD: + result += numbers[i]; + break; + case TOper::SUB: + result -= numbers[i]; + break; + case TOper::MUL: + result *= numbers[i]; + break; + case TOper::DIV: + result /= numbers[i]; + break; + default: + std::cout << "exe error" << std::endl; + break; + } + } + numbers.clear(); + Token nt(true, TOper::NONE, result); + stk_.push(nt); + std::cout << result << std::endl; + rec = result; + } + + if(!rc) break; + } + return rec; +} diff --git a/Calculator.h b/Calculator.h new file mode 100644 index 0000000..1743235 --- /dev/null +++ b/Calculator.h @@ -0,0 +1,16 @@ +#ifndef CALCULATOR_H +#define CALCULATOR_H + +#include "Stack.h" + +class Calculator +{ +public: + Calculator(Stack &stk); + double exe();///TODO rename execute + +private: + Stack stk_; +}; + +#endif // CALCULATOR_H diff --git a/Parser.cpp b/Parser.cpp new file mode 100644 index 0000000..b8b0348 --- /dev/null +++ b/Parser.cpp @@ -0,0 +1,49 @@ +#include "Parser.h" + +#include //just for test +#include + +Parser::Parser(const std::vector & line) + : rawLines(line) + , currentPosition(0) +{} + +void Parser::parse() +{ + for(size_t i = 0; i < rawLines.size(); ++i) { + currentPosition = i+1; + //check for operator + ///TODO: check Factory applicance + if(rawLines[i] == std::string("+")) { + Token t(TOper::ADD); + tokens.push_back(t); + } else if (rawLines[i] == std::string("-")) { + Token t(TOper::SUB); + tokens.push_back(t); + } else if (rawLines[i] == std::string("*")) { + Token t(TOper::MUL); + tokens.push_back(t); + } else if (rawLines[i] == std::string("/")) { + Token t(TOper::DIV); + tokens.push_back(t); + } else { //check for number + long double dd; +// try { + dd = std::stod (rawLines[i]); + //try to throw position also +// } catch (...) { +// std::cerr << "wrong data on " << i+1 +// << " position" << std::endl; +// return; +// } + + Token t(dd); + tokens.push_back(t); + } + } +} + +const std::vector &Parser::getTokens() +{ + return tokens; +} diff --git a/Parser.h b/Parser.h new file mode 100644 index 0000000..92261d1 --- /dev/null +++ b/Parser.h @@ -0,0 +1,20 @@ +#ifndef PARSER_H +#define PARSER_H + +#include "Token.h" +#include + +class Parser +{ +public: + Parser(const std::vector & line); + void parse(); + const std::vector& getTokens(); + size_t getPositionOfErrorInRawLine() const; +private: + std::vector tokens; + std::vector rawLines; + size_t currentPosition; +}; + +#endif // PARSER_H diff --git a/Stack.cpp b/Stack.cpp new file mode 100644 index 0000000..1b68333 --- /dev/null +++ b/Stack.cpp @@ -0,0 +1,45 @@ +#include "Stack.h" +#include + +Stack::Stack(const std::vector& t) : size(0) /*: size(t.size()) */ //init vector +{ + //for(int i = size-1; i >= 0; --i) { + for(int i = t.size()-1; i >= 0; --i) { + push(t[i]); + } +} + +Stack::~Stack() +{ + size = 0; + stk.clear(); +} + +int Stack::getSize() const +{ + return size; +} + +void Stack::push(const Token& elem) +{ + if ( stk.size() <= size ) { // here size must be added after compate + stk.push_back(elem); + ++size; + } else + std::cout << "stack is full" << std::endl; +} + +Token Stack::pop(int &rc) +{ + rc = 1; + if ( stk.empty() ) { + std::cout << "stack is empty" << std::endl; + //return -EINVAL; + rc = 0; + } else { + Token temp = stk.back(); + stk.pop_back(); + --size; + return temp; + } +} diff --git a/Stack.h b/Stack.h new file mode 100644 index 0000000..54744bf --- /dev/null +++ b/Stack.h @@ -0,0 +1,22 @@ +#ifndef STACK_H +#define STACK_H + +#include +#include +#include "Token.h" + +///TODO:namespace +class Stack { + int size; + std::vector stk; +public: + ///TODO: stack is stack. do adapter for stack with Token + Stack(const std::vector& t); + ~Stack(); + void push(const Token& elem); + Token pop(int &rc); ///TODO: think about another one interface + int getSize() const; +}; + + +#endif // STACK_H diff --git a/Token.cpp b/Token.cpp new file mode 100644 index 0000000..826937e --- /dev/null +++ b/Token.cpp @@ -0,0 +1,40 @@ +#include "Token.h" + +Token::Token(const bool is_number, const TOper::Operand opr, const long double num) + : is_num(is_number) +{ + if(is_num) { + d = num; + this->opr = TOper::NONE; + } else { + this->opr = opr; + d = 0; //default + } +} + +Token::Token(const long double num) + : is_num(true) + , d(num) + , opr(TOper::NONE) +{} + +Token::Token(const TOper::Operand opr) + : is_num(false) + , d(0) + , opr(opr) +{} + +TOper::Operand Token::getOperand() +{ + return opr; +} + +long double Token::getNumber() +{ + return d; +} + +bool Token::is_number() +{ + return is_num; +} diff --git a/Token.h b/Token.h new file mode 100644 index 0000000..c19514b --- /dev/null +++ b/Token.h @@ -0,0 +1,36 @@ +#ifndef TOKEN_H +#define TOKEN_H + +#include + +namespace TOper { + +///TODO: rename +///TODO: enum class +enum Operand { + ADD, + SUB, + DIV, + MUL, + NONE +}; + +} + +class Token +{ +public: + ///TODO: split to different constructors + Token(const bool is_number, const TOper::Operand opr, const long double num); + Token(const long double num); + Token(const TOper::Operand opr); + bool is_number(); + TOper::Operand getOperand(); + long double getNumber(); +private: + long double d; + TOper::Operand opr; + bool is_num; +}; + +#endif // TOKEN_H diff --git a/executor.cpp b/executor.cpp new file mode 100644 index 0000000..f0b7a83 --- /dev/null +++ b/executor.cpp @@ -0,0 +1,16 @@ +#include "executor.h" + +#include "Parser.h" +#include "Stack.h" +#include "Calculator.h" +#include "executor.h" + +double execute(const std::vector & src) +{ + Parser p(src); + p.parse(); + Stack stk(p.getTokens()); + //std::cout << "test" << std::endl; + Calculator calc(stk); + return calc.exe(); +} diff --git a/executor.h b/executor.h new file mode 100644 index 0000000..82b1f70 --- /dev/null +++ b/executor.h @@ -0,0 +1,9 @@ +#ifndef EXECUTOR_H +#define EXECUTOR_H + +#include +#include +///TODO: namespace wrrap +double execute(const std::vector & src); + +#endif // EXECUTOR_H diff --git a/main.cc b/main.cc index 5b2357f..73c8621 100644 --- a/main.cc +++ b/main.cc @@ -1,5 +1,64 @@ #include "project.h" -int main() { - return 0; +#include "executor.h" + +#include +#include +#include +using namespace std; // test + +/* + * The calculator input is double numbers, operators + - * / + * The maximum input line is 255 + * + * */ + +static int show_usage() +{ + std::cerr << "Usage: || " + << "Options:\n" + << "\t-h,--help\t\tShow this help message\n" + << "Valid arguments:\n" + << "\tSpecify target string after program name\n" + << "\tNumbers with point and operations + - / * with space separation" + << std::endl; + return -1; +} + +int main(int argc, char** argv) { + + try { + if (argc < 2 /*|| argc >= 12*/) { + show_usage(); + return 1; + } + + std::vector sources; + for (int i = 1; i < argc; ++i) { + std::string arg = argv[i]; + if ((arg == "-h") || (arg == "--help")) { + show_usage(); + return 0; + } else { + sources.push_back(arg); + } + } + + execute(sources); + } catch (const std::invalid_argument& e) { + std::cout << "std::invalid_argument" << std::endl; + show_usage(); + } catch (const std::out_of_range& e) { + std::cout << "std::out_of_range" << std::endl; + show_usage(); + } catch (...) { + /* +If no conversion could be performed, an invalid_argument exception is thrown. +If the value read is out of the range of representable values by a double (in some library implementations, this includes underflows), an out_of_range exception is thrown. + */ + + show_usage(); + } + + return 0; } diff --git a/project.cc b/project.cc index fdc1225..38b4fbf 100644 --- a/project.cc +++ b/project.cc @@ -1,8 +1,29 @@ #include "project.h" +#include "Stack.h" +#include "Token.h" +#include "Calculator.h" +#include "Parser.h" + +#include namespace dev { +void Project::stackTest() +{ + /* + std::cout << "stack" << std::endl; + std::vector tk; + tk.push_back(Token(true, TOper::NONE, 3)); + tk.push_back(Token(true, TOper::NONE, 2)); + tk.push_back(Token(true, TOper::PLUS, 0)); + Stack stk(tk); + std::cout << "test" << std::endl; + */ +} + int Project::run() { + stackTest(); + std::cout << "rrrrrrrrun" << std::endl; return 0; } } // namespace dev diff --git a/project.h b/project.h index b5cc813..b94c803 100644 --- a/project.h +++ b/project.h @@ -7,5 +7,6 @@ class Project : public IProject { // IProject interface public: int run(); + void stackTest(); }; } // namespace dev diff --git a/test/project_test.cc b/test/project_test.cc index 5dbeb2f..6f4b77f 100644 --- a/test/project_test.cc +++ b/test/project_test.cc @@ -1,19 +1,248 @@ #include "project.h" #include #include + +#include + +#include "Token.h" +#include "Stack.h" +#include "Parser.h" +#include "executor.h" + namespace dev { namespace testing { +/* + * List of tests: + * -ввод одной цифры + * -ввод однонго символа + * -ввод одной операции + * -ввод неправильной последовательности + 2 3 + * -ввод одних символов + * -ввод дробных чисел + * -ряд тестов с простыми операциями + - * / + * -ввод отрицательных чисел + * -такая комбинация чисел при которой получается отрицательный результат + * -сложное вырежение. 15 7 1 1 + - / 3 \* 2 1 1 + + - + * -пустая строка + * -нет операции(одни числа) + * -деление на 0 + * -лишнее операнды + * -числа overflow + * -попробовать вызвать exec с вызовом программы с параметрами. + * + * 5 2 \* 2 / 6.5 - + * + * вопрос. + * хороший тест вызов одной функции на одну строку + * не пишем специальных функций для тестов. + * + * */ + class ProjectTest : public ::testing::Test { public: - void SetUp() override {} - void TearDown() override {} + void SetUp() override; + void TearDown() override; dev::Project project_; }; +void ProjectTest::SetUp() +{ + std::cout << "SetUp" << std::endl; +} + +void ProjectTest::TearDown() +{ + std::cout << "TearDown" << std::endl; +} + TEST_F(ProjectTest, Run) { + + std::cout << "lalala" << std::endl; ASSERT_EQ(0, project_.run()); } +//Token test start +TEST_F(ProjectTest, TokenNumber) { + constexpr double test_number = -9.985; + Token t(test_number); + ASSERT_DOUBLE_EQ(test_number, t.getNumber()); + ASSERT_EQ(true, t.is_number()); + ASSERT_EQ(TOper::NONE, t.getOperand()); +} + +TEST_F(ProjectTest, TokenOperation) { + TOper::Operand test_operation = TOper::DIV; + Token t(test_operation); + ASSERT_EQ(0, t.getNumber()); + ASSERT_EQ(false, t.is_number()); + ASSERT_EQ(test_operation, t.getOperand()); +} + +TEST_F(ProjectTest, TokenComplexConstructor) { + constexpr double test_number = -9.985; + Token t(true, TOper::ADD, test_number); + ASSERT_DOUBLE_EQ(test_number, t.getNumber()); + ASSERT_EQ(true, t.is_number()); + ASSERT_EQ(TOper::NONE, t.getOperand()); + + Token t2(false, TOper::ADD, test_number); + ASSERT_EQ(0, t2.getNumber()); + ASSERT_EQ(false, t2.is_number()); + ASSERT_EQ(TOper::ADD, t2.getOperand()); +} +//Token test end + +//Stack test start +TEST_F(ProjectTest, Stack) { + std::vector tk; + tk.push_back(Token(true, TOper::NONE, 3)); + tk.push_back(Token(true, TOper::NONE, 2)); + tk.push_back(Token(false, TOper::ADD, 0)); + tk.push_back(Token(true, TOper::NONE, 1)); + tk.push_back(Token(false, TOper::SUB, 0)); + Stack stk(tk); + + int rc = 0; + Token t = stk.pop(rc); + ASSERT_EQ(1, rc); + ASSERT_EQ(1, t.is_number()); + ASSERT_EQ(t.getNumber(), 3); + ASSERT_EQ(4, stk.getSize()); + + t = stk.pop(rc); + ASSERT_EQ(1, rc); + ASSERT_EQ(1, t.is_number()); + ASSERT_EQ(t.getNumber(), 2); + ASSERT_EQ(stk.getSize(), 3); + + t = stk.pop(rc); + +// std::cout << "test " << t.is_number() << std::endl; +// std::cout << t.getNumber() << std::endl; +// std::cout << t.getOperand() << std::endl; + + ASSERT_EQ(1, rc); + ASSERT_EQ(0, t.is_number()); + ASSERT_EQ(t.getOperand(), TOper::ADD); + ASSERT_EQ(stk.getSize(), 2); + + t = stk.pop(rc); + ASSERT_EQ(1, rc); + ASSERT_EQ(1, t.is_number()); + ASSERT_EQ(t.getNumber(), 1); + ASSERT_EQ(stk.getSize(), 1); + + t = stk.pop(rc); + ASSERT_EQ(1, rc); + ASSERT_EQ(false, t.is_number()); + ASSERT_EQ(t.getOperand(), TOper::SUB); + ASSERT_EQ(stk.getSize(), 0); + + t = stk.pop(rc); + ASSERT_EQ(0, rc); +} +//Stack test end + +//Parser test start +TEST_F(ProjectTest, Parser) { + std::vector customLine; + customLine.push_back(std::string("638763")); + customLine.push_back(std::string("0.2121")); + customLine.push_back(std::string("-0.212")); + customLine.push_back(std::string("-8678")); + customLine.push_back(std::string("-")); + customLine.push_back(std::string("+")); + customLine.push_back(std::string("/")); + customLine.push_back(std::string("*")); + customLine.push_back(std::string("-0,212")); + //can`t check wrong input because of try catch =(( + + Parser p1(customLine); + p1.parse(); + + std::vector resultTokins(p1.getTokens()); + ASSERT_EQ(resultTokins[0].getNumber(), 638763); + ASSERT_DOUBLE_EQ(resultTokins[1].getNumber(), 0.2121); + ASSERT_DOUBLE_EQ(resultTokins[2].getNumber(), -0.212); + ASSERT_EQ(resultTokins[3].getNumber(), -8678); + ASSERT_EQ(resultTokins[4].getOperand(), TOper::SUB); + ASSERT_EQ(resultTokins[5].getOperand(), TOper::ADD); + ASSERT_EQ(resultTokins[6].getOperand(), TOper::DIV); + ASSERT_EQ(resultTokins[7].getOperand(), TOper::MUL); + ASSERT_DOUBLE_EQ(resultTokins[2].getNumber(), -0.212); +} +//Parser test end + + +//Calculator test start +TEST_F(ProjectTest, simpleAdd) { + std::vector sources; + sources.push_back(std::string("5")); + sources.push_back(std::string("2")); + sources.push_back(std::string("+")); + + ASSERT_DOUBLE_EQ(execute(sources), 7); +} + +TEST_F(ProjectTest, simpleSub) { + std::vector sources; + sources.push_back(std::string("5")); + sources.push_back(std::string("2")); + sources.push_back(std::string("-")); + + ASSERT_DOUBLE_EQ(execute(sources), 3); +} + +TEST_F(ProjectTest, simpleMul) { + std::vector sources; + sources.push_back(std::string("5")); + sources.push_back(std::string("2")); + sources.push_back(std::string("*")); + + ASSERT_DOUBLE_EQ(execute(sources), 10); +} + +TEST_F(ProjectTest, simpleDiv) { + std::vector sources; + sources.push_back(std::string("5")); + sources.push_back(std::string("2")); + sources.push_back(std::string("/")); + + ASSERT_DOUBLE_EQ(execute(sources), 2.5); +} + +TEST_F(ProjectTest, mediumRear) { + std::vector sources; + sources.push_back(std::string("5")); + sources.push_back(std::string("2")); + sources.push_back(std::string("/")); + sources.push_back(std::string("2")); + sources.push_back(std::string("*")); + sources.push_back(std::string("1")); + sources.push_back(std::string("-")); + sources.push_back(std::string("2")); + sources.push_back(std::string("*")); + + ASSERT_DOUBLE_EQ(execute(sources), 8); +} + +TEST_F(ProjectTest, wrongImput) { + std::vector sources; + sources.push_back(std::string("5")); + sources.push_back(std::string("2")); + sources.push_back(std::string("/")); + sources.push_back(std::string("2")); + sources.push_back(std::string("*")); + sources.push_back(std::string("1")); + sources.push_back(std::string("-")); + sources.push_back(std::string("2")); + sources.push_back(std::string("H")); + + ASSERT_THROW(execute(sources), std::invalid_argument); +} + +//Calculator test end + } // namespace testing } // namespace dev