Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
build/

# Prerequisites
*.d

Expand Down
14 changes: 14 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
cmake_minimum_required(VERSION 3.5)
project(ArithmeticParser LANGUAGES CXX)

set(CMAKE_CXX_STANDARD 14)
set(CMAKE_CXX_EXTENSIONS OFF)
set(CMAKE_CXX_STANDARD_REQUIRED TRUE)

if(NOT CMAKE_BUILD_TYPE)
set(CMAKE_BUILD_TYPE Release)
endif()

FILE(GLOB SRC_HPP "src/*.hpp")

add_executable(${PROJECT_NAME} "src/main.cpp" ${SRC_HPP})
51 changes: 51 additions & 0 deletions src/Computer.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
#pragma once
#include "Token.hpp"
#include "Variables.hpp"
#include "Expression.hpp"
#include "Operations.hpp"

namespace ap
{
class Computer
{
public:
Computer(const Expression& exprGraphg, const Variables& variables) :
_graph(exprGraphg),
_variables(variables)
{ }

double compute()
{
return compute(_graph);
}

private:
const Expression& _graph;
const Variables& _variables;

double compute(const Expression& expr)
{
TokenType type = expr.token.type;
if (type == TokenType::DIGIT)
return std::stod(expr.token.expr);
else if (type == TokenType::VARIABLE)
return _variables.findVariable(expr.token.expr)->second;
else if (type == TokenType::UNARY_OPERATOR)
return applyUnaryOperation(expr.token.expr, expr.operands[0]);
else if (type == TokenType::BINARY_OPERATOR)
return applyBinaryOperation(expr.token.expr, expr.operands[0], expr.operands[1]);
else
throw std::runtime_error("unordered TokenType");
}

double applyUnaryOperation(const std::string& operation, const Expression& expr)
{
return Operations::Instance().findUnaryBySymbols(operation)->compute(compute(expr));
}

double applyBinaryOperation(const std::string& operation, const Expression& expr1, const Expression& expr2)
{
return Operations::Instance().findBinaryBySymbols(operation)->compute(compute(expr1), compute(expr2));
}
};
}
38 changes: 38 additions & 0 deletions src/Exceptions.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
#pragma once
#include <string>
#include <stdexcept>

namespace ap
{
class UnorderedSymbols : public std::runtime_error
{
public:
UnorderedSymbols(size_t where, const std::string& ch) :
std::runtime_error("Unordered symbols at " + std::to_string(where) + " : \"" + ch + "\"")
{ }

UnorderedSymbols(size_t where, char ch) :
UnorderedSymbols(where, std::string() + ch)
{ }
};

class UnexpectedSymbols : public std::runtime_error
{
public:
UnexpectedSymbols(size_t where, const std::string& ch) :
std::runtime_error("Unexpected symbols at " + std::to_string(where) + " : \"" + ch + "\"")
{ }

UnexpectedSymbols(size_t where, char ch) :
UnexpectedSymbols(where, std::string() + ch)
{ }
};

class ExpectedSymbols : public std::runtime_error
{
public:
ExpectedSymbols(size_t where, const std::string& expectedChar) :
std::runtime_error("Symbol \"" + std::string(expectedChar) + std::string("\" expected at ") + std::to_string(where))
{ }
};
}
27 changes: 27 additions & 0 deletions src/Expression.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
#pragma once
#include <string>
#include <vector>
#include "Token.hpp"

namespace ap
{
struct Expression
{
Expression(Token digit) :
token(digit)
{ }

Expression(Token operation, Expression operand) :
token(operation),
operands{ operand }
{ }

Expression(Token operation, Expression operand1, Expression operand2) :
token(operation),
operands{ operand1, operand2 }
{ }

Token token;
std::vector<Expression> operands;
};
}
135 changes: 135 additions & 0 deletions src/Operations.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
#pragma once
#include <vector>

namespace ap
{
class IUnaryOperation
{
public:
virtual std::string getSymbols() = 0;
virtual double compute(double arg) = 0;
};

class IBinaryOperation
{
public:
virtual int getPriority() = 0;
virtual std::string getSymbols() = 0;
virtual double compute(double arg1, double arg2) = 0;
};


class UnaryMinus : public IUnaryOperation
{
virtual std::string getSymbols() override { return "-"; }
virtual double compute(double arg) override { return -arg; }
};

class Sinus : public IUnaryOperation
{
virtual std::string getSymbols() override { return "sin"; }
virtual double compute(double arg) override { return std::sin(arg); }
};

class Cosinus : public IUnaryOperation
{
virtual std::string getSymbols() override { return "cos"; }
virtual double compute(double arg) override { return std::cos(arg); }
};

class BinaryPlus : public IBinaryOperation
{
public:
virtual int getPriority() override { return 10; }
virtual std::string getSymbols() override { return "+"; }
virtual double compute(double arg1, double arg2) override { return arg1 + arg2; }
};

class BinaryMinus : public IBinaryOperation
{
public:
virtual int getPriority() override { return 10; }
virtual std::string getSymbols() override { return "-"; }
virtual double compute(double arg1, double arg2) override { return arg1 - arg2; }
};

class Multiplication : public IBinaryOperation
{
public:
virtual int getPriority() override { return 30; }
virtual std::string getSymbols() override { return "*"; }
virtual double compute(double arg1, double arg2) override { return arg1 * arg2; }
};

class Division : public IBinaryOperation
{
public:
virtual int getPriority() override { return 40; }
virtual std::string getSymbols() override { return "/"; }
virtual double compute(double arg1, double arg2) override { return arg1 / arg2; }
};

class Pow : public IBinaryOperation
{
public:
virtual int getPriority() override { return 50; }
virtual std::string getSymbols() override { return "^"; }
virtual double compute(double arg1, double arg2) override { return std::pow(arg1, arg2); }
};

// ��� ���������� �������� - �������� �� ��������� � ������ ���������������� ���� �������� � Operations()
class Operations
{
using TUnarys = std::vector<std::unique_ptr<IUnaryOperation>>;
using TBinarys = std::vector<std::unique_ptr<IBinaryOperation>>;
public:
static Operations& Instance()
{
static Operations operations;
return operations;
}

IUnaryOperation* findUnaryBySymbols(const std::string& symbols) const
{
for (const auto& operation : _unarys)
if (std::strncmp(operation->getSymbols().c_str(), symbols.c_str(), operation->getSymbols().size()) == 0)
return operation.get();
return nullptr;
Comment on lines +94 to +97
Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

можно использовать std::find
только нужно перегрузить оператор ==
можно классы IUnaryOperation, IBinaryOperation,... унаследовать от общего класса с перегруженным ==, равенство можно проверять через getSymbols
Правда вопрос, что делать с унарным и бинарным минусом (dynamic_cast?)

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Да, так можно было сделать, но две эти функции все равно нельзя было бы объединить в одну, т.к. для корректного анализа вводимых данных парсер ожидает в зависимости от предыдущего токена операцию конкретного типа (это как раз таки сделано во избежание конфликта операций разных типов, имеющих одно обозначение, как "-").

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

тут проще добавить поле с уникальным id (для каждого нового класса) и делать проверку по нему

}

IBinaryOperation* findBinaryBySymbols(const std::string& symbols) const
{
for (const auto& operation : _binarys)
if (std::strncmp(operation->getSymbols().c_str(), symbols.c_str(), operation->getSymbols().size()) == 0)
return operation.get();
return nullptr;
}

const TUnarys& getUnarys()
{
return _unarys;
}

const TBinarys& getBynarys()
{
return _binarys;
}

private:
TUnarys _unarys;
TBinarys _binarys;
Comment on lines +119 to +120
Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Казалось бы _unarys, _binarys можно сделать статическими, все остальные методы тоже. Тогда получается можно и без паттерна Singleton можно обойтись? Или нет? А если можно обойтись, то почему (и в каких случаях) это хуже?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

В принципе, любой шаблон проектирования имеет более "топорные" аналоги. В данном случае замена на статические списки и правда не дала бы заметных изменений, но существуют ситуации, например, где экземпляр класса имеет промежуточные состояния, для которых также бы пришлось заводить отдельные переменные и неиспользуемые пользователем (приватные) методы, что привело бы к загромождению рабочего пространства и возможным ошибкам использования сущности.


Operations()
{
_unarys.emplace_back(new UnaryMinus());
_unarys.emplace_back(new Sinus());
_unarys.emplace_back(new Cosinus());

_binarys.emplace_back(new BinaryPlus());
_binarys.emplace_back(new BinaryMinus());
_binarys.emplace_back(new Multiplication());
_binarys.emplace_back(new Division());
_binarys.emplace_back(new Pow());
}
Comment on lines +122 to +133
Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Конструктор копирования, оператор присваивания наверно тоже нужно сделать приватными. Если этого не сделать, ведь их можно будет вызвать?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Да, это действительно стоило сделать.

};
}
Loading