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
14 changes: 14 additions & 0 deletions lab1/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
cmake_minimum_required(VERSION 3.27)

project(calculator
VERSION 0.1.0
DESCRIPTION "Simple calculator <3"
LANGUAGES CXX
)

SET(CMAKE_CXX_STANDARD 20)
SET(CMAKE_CXX_STANDARD_REQUIRED ON)

add_subdirectory(default_operators)
add_subdirectory(dll_plugins)
add_subdirectory(calculator)
21 changes: 21 additions & 0 deletions lab1/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# Calculator

Приложение состоит из исполняемого файла - .exe, который имеет консольный интерфейс и
способен вычислять выражения с операциями + - / * ().
- Ввод: 16 + 4 * (3 – 1)
- Вывод: 24

При запуске приложение обращается в папку ./plugins, и из каждой dll загружает и распознает
соответствующую функцию.
- ./calc.exe
- ./plugins/funcsin.dll
- ./plugins/funcdeg.dll

Что приводит к возможности использовать эти функции сразу же после запуска приложения, делая валидными вычисления:
- 2^4 + sin(90)

Примечания:
- Каждый плагин содержит ровно одну функцию.
- Имя файла dll не должно участвовать на
определение самой функции и ее имени.
- Необходимо определить и обработать исключительные случаи (некорректная dll, нет никаких dll, какая-то из функций бросает исключение, например, ln(-100) )
16 changes: 16 additions & 0 deletions lab1/calc_custom_exception/CalcException.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
#pragma once

#include <iostream>
#include <format>


class CalcException : public std::exception {
std::string const message;
public:
explicit CalcException(std::string const &tag, std::string const &description) : message(
std::format("{}: {}", tag, description)) {}

char const *what() const noexcept override {
return message.c_str();
}
};
34 changes: 34 additions & 0 deletions lab1/calculator/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
cmake_minimum_required(VERSION 3.27)

project(calc)

SET(CMAKE_CXX_STANDARD 20)
SET(CMAKE_CXX_STANDARD_REQUIRED ON)

SET(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/../out)

SET(SOURCE_FILES
source/main.cpp
source/Calculator/TokenScanner.cpp
source/Calculator/TokenConverter.cpp
source/Calculator/PolishCalculator.cpp
source/Calculator/Calculator.cpp
source/Calculator/PluginsLoader/PluginsLoader.cpp
)

add_executable(${PROJECT_NAME} ${SOURCE_FILES})


set(TOKEN_I_SOURCE ${CMAKE_CURRENT_SOURCE_DIR}/../token_default_classes)
set(DEFAULT_OP_SOURCE ${CMAKE_CURRENT_SOURCE_DIR}/../default_operators)
set(PLUGINS_SOURCE ${CMAKE_CURRENT_SOURCE_DIR}/../dll_plugins)
set(CALC_EXCEPTION_SOURCE ${CMAKE_CURRENT_SOURCE_DIR}/../calc_custom_exception)

target_include_directories(${PROJECT_NAME} PRIVATE
${TOKEN_I_SOURCE}
${DEFAULT_OP_SOURCE}/include
${PLUGINS_SOURCE}/include
${CALC_EXCEPTION_SOURCE}
)

target_link_libraries(${PROJECT_NAME} PRIVATE ${DEFAULT_OP_SOURCE}/lib/default_operators.lib)
31 changes: 31 additions & 0 deletions lab1/calculator/source/CLI.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
#pragma once
#include <iostream>

#include "CalcException.hpp"


class CLI {
public:
static void startInteraction() {
std::cout << R"(print "exit" or ":q" to leave)" << std::endl;

std::string exprStr;
Calculator calculator;
while (true) {
std::cout << "> ";
std::getline(std::cin, exprStr);

if (exprStr == "exit" || exprStr == ":q")
return;

try {
std::cout << std::format("{} = {}\n", exprStr, calculator.calculate(exprStr));
} catch (CalcException& exception) {
std::cout << exception.what() << std::endl;
} catch (std::exception& exception) {
std::cout << std::format("Caught exception during program runtime\nwhat: {}", exception.what());
exit(1);
}
}
}
};
6 changes: 6 additions & 0 deletions lab1/calculator/source/Calculator/Calculator.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
#include "Calculator.hpp"


double Calculator::calculate(std::string const& exprStr) {
return polishCalculator.calculate(*converter.convert(*scanner.buildTokens(exprStr)));
}
15 changes: 15 additions & 0 deletions lab1/calculator/source/Calculator/Calculator.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
#pragma once
#include <string>

#include "TokenScanner.hpp"
#include "TokenConverter.hpp"
#include "PolishCalculator.hpp"


class Calculator {
TokenConverter converter;
TokenScanner scanner;
PolishCalculator polishCalculator;
public:
[[nodiscard]] double calculate(std::string const& exprStr);
};
61 changes: 61 additions & 0 deletions lab1/calculator/source/Calculator/PluginsLoader/PluginsLoader.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
#include "PluginsLoader.hpp"

#include <windows.h>
#include <tchar.h>
#include <filesystem>
#include <iostream>

#include "export.hpp"


void PluginsLoader::loadPlugins() {
loadedPlugins.clear();
for (auto const &entry: std::filesystem::directory_iterator(dllDir)) {
HMODULE dllHandler = LoadLibrary(_T(entry.path().string().c_str()));
if (!dllHandler) {
continue;
}
FARPROC getNameFuncAddr = GetProcAddress(dllHandler, "getName");
FARPROC calcFuncAddr = GetProcAddress(dllHandler, "calc");
FARPROC getTypeFuncAddr = GetProcAddress(dllHandler, "getType");
FARPROC getPriorLevelFuncAddr = GetProcAddress(dllHandler, "getPriorityLevel");

if (!calcFuncAddr || !getNameFuncAddr || !getTypeFuncAddr || !getPriorLevelFuncAddr)
continue;

std::function getNameFunc((decltype(&::getName)(getNameFuncAddr)));
std::function calcFunc((decltype(&::calc)(calcFuncAddr)));
std::function getTypeFunc((decltype(&::getType)(getTypeFuncAddr)));
std::function getPriorityLevelFunc((decltype(&::getPriorityLevel)(getPriorLevelFuncAddr)));

nameToFunc.emplace(
getNameFunc(),
CalcFuncWithInfo{
getTypeFunc(),
calcFunc,
getPriorityLevelFunc()
});
loadedPlugins.emplace_back(dllHandler);
}
}

bool PluginsLoader::contains(std::string const &opName, Tok::TokenType opType) {
return nameToFunc.contains(opName) && nameToFunc.at(opName).type == opType;
}

std::function<double(std::vector<double> const &)> &PluginsLoader::getOpFunction(std::string const &opName) {
return nameToFunc.at(opName).calcFunc;
}

uint8_t PluginsLoader::getPriorityLevel(std::string const &opName) {
return nameToFunc.at(opName).priorityLevel;
}

void PluginsLoader::freePlugins() {
loadedPlugins.clear();
}


PluginsLoader::DllWatcher::~DllWatcher() {
FreeLibrary(dllHandler);
}
46 changes: 46 additions & 0 deletions lab1/calculator/source/Calculator/PluginsLoader/PluginsLoader.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
#pragma once

#include <windef.h>
#include <unordered_map>
#include <string>
#include <functional>
#include <utility>
#include <vector>
#include <list>
#include "Token.hpp"


class PluginsLoader {
class DllWatcher {
HMODULE dllHandler;
public:
explicit DllWatcher(HMODULE dllHandler) : dllHandler(dllHandler) {}

~DllWatcher();
};

class CalcFuncWithInfo {
public:
Tok::TokenType type;
std::function<double(std::vector<double> const &)> calcFunc;
uint8_t priorityLevel;
};

std::wstring const dllDir;
std::list<DllWatcher> loadedPlugins;
std::unordered_map<std::string, CalcFuncWithInfo> nameToFunc;
public:
PluginsLoader(std::wstring dllDir = L"plugins") : dllDir(std::move(dllDir)) {}

~PluginsLoader() { freePlugins(); }

void loadPlugins();

bool contains(std::string const &opName, Tok::TokenType opType);

std::function<double(std::vector<double> const &)> &getOpFunction(std::string const &opName);

uint8_t getPriorityLevel(std::string const &opName);

void freePlugins();
};
35 changes: 35 additions & 0 deletions lab1/calculator/source/Calculator/PolishCalculator.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
#include "PolishCalculator.hpp"

#include "ComputableOperator.hpp"


double PolishCalculator::calculate(std::queue<Tok::TokenPtr>& tokens) {
operandStack = std::make_shared<std::stack<Tok::OperandPtr>>();

while (!tokens.empty()) {
auto token = tokens.front();
tokens.pop();
parseToken(token);
}

auto result = operandStack->top()->getValue();
operandStack.reset();

return result;
}

void PolishCalculator::parseToken(Tok::TokenPtr &token) {
switch (token->getType()) {
case Tok::PREFIX_OPERATOR:
case Tok::SUFFIX_OPERATOR:
case Tok::POSTFIX_OPERATOR:
std::dynamic_pointer_cast<Tok::ComputableOperator>(token)->doCalc(*operandStack);
break;
case Tok::OPERAND:
operandStack->push(std::dynamic_pointer_cast<Tok::Operand>(token));
break;
case Tok::OPENING_PARENTHESIS:
case Tok::CLOSING_PARENTHESIS:
break;
}
}
17 changes: 17 additions & 0 deletions lab1/calculator/source/Calculator/PolishCalculator.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
#pragma once

#include <memory>
#include <queue>
#include <stack>

#include "Token.hpp"
#include "Operand.hpp"


class PolishCalculator {
std::shared_ptr<std::stack<Tok::OperandPtr>> operandStack;

void parseToken(Tok::TokenPtr &token);
public:
double calculate(std::queue<Tok::TokenPtr> &tokens);
};
87 changes: 87 additions & 0 deletions lab1/calculator/source/Calculator/TokenConverter.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
#include "TokenConverter.hpp"

#include <format>
#include <iostream>

#include "Parenthesis.hpp"
#include "CalcException.hpp"


void TokenConverter::dropOperators(Tok::OperatorPtr const &op) {
while (!operatorsStack->empty() && *op <= *operatorsStack->top()) {
resultTokens->push(operatorsStack->top());
operatorsStack->pop();
}
}

void TokenConverter::parseForPrefix(std::shared_ptr<Tok::Token> &token) {
if (token->getType() == Tok::TokenType::OPERAND) {
resultTokens->push(token);
state = ConvertState::WAITING_FOR_SUFFIX;
return;
}

auto opToken = std::dynamic_pointer_cast<Tok::Operator>(token);
if (opToken->getType() == Tok::TokenType::OPENING_PARENTHESIS ||
opToken->getType() == Tok::TokenType::PREFIX_OPERATOR) {
operatorsStack->push(opToken);
} else {
throw CalcException("ConvertError", std::format("Expected number or \"(\", found {}", opToken->getName()));
}
}

void TokenConverter::parseForSuffix(std::shared_ptr<Tok::Token> &token) {
if (token->getType() == Tok::TokenType::OPERAND)
throw CalcException("ConvertError", "Expected binary operator or \")\", found number");

auto opToken = std::dynamic_pointer_cast<Tok::Operator>(token);
dropOperators(opToken);

if (opToken->getType() == Tok::TokenType::OPENING_PARENTHESIS &&
(operatorsStack->empty() || operatorsStack->top()->getType() != Tok::TokenType::CLOSING_PARENTHESIS))
throw CalcException("ConvertError", "Expected \"(\"");

if (opToken->getType() == Tok::TokenType::CLOSING_PARENTHESIS)
operatorsStack->pop();
else if (opToken->getType() != Tok::TokenType::CLOSING_PARENTHESIS) {
operatorsStack->push(opToken);
if (opToken->getType() != Tok::TokenType::POSTFIX_OPERATOR)
state = ConvertState::WAITING_FOR_PREFIX;
}
}

std::shared_ptr<std::queue<Tok::TokenPtr>> TokenConverter::convert(std::queue<Tok::TokenPtr> &tokens) {
resultTokens = std::make_shared<std::queue<Tok::TokenPtr>>();
operatorsStack = std::make_unique<std::stack<Tok::OperatorPtr>>();

std::shared_ptr<Tok::Token> token;
state = ConvertState::WAITING_FOR_PREFIX;

while (true) {
if (tokens.empty() && state == ConvertState::WAITING_FOR_PREFIX)
throw CalcException("ConvertError", "Unexpected end of the expression");
if (tokens.empty() && state == ConvertState::WAITING_FOR_SUFFIX) {
break;
} else {
token = tokens.front();
tokens.pop();
}

switch (state) {
case WAITING_FOR_PREFIX:
parseForPrefix(token);
break;
case WAITING_FOR_SUFFIX:
parseForSuffix(token);
break;
}
}

dropOperators(std::make_shared<Tok::ClosingParenthesis>());
if (!operatorsStack->empty())
throw CalcException("ConvertError", "Expected \")\"");
operatorsStack.reset();

return resultTokens;
}

Loading