diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..af235cf --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,46 @@ +cmake_minimum_required(VERSION 3.15) + +project(CalculatorApp LANGUAGES CXX) + +set(CMAKE_CXX_STANDARD 17) +set(CMAKE_CXX_STANDARD_REQUIRED ON) + +# ------------------------------ +# ��������� ������������ +# ------------------------------ +add_executable(calc + src/main.cpp + src/calculator.cpp + src/plugin_manager.cpp +) + +target_include_directories(calc PRIVATE src) + +# ------------------------------ +# ����� ��� DLL-�������� +# ------------------------------ +set(PLUGIN_OUTPUT_DIR ${CMAKE_BINARY_DIR}/plugins) +file(MAKE_DIRECTORY ${PLUGIN_OUTPUT_DIR}) + +# ------------------------------ +# ������ ��� ������ ������ ������� +# ------------------------------ +function(add_calc_plugin NAME SRCFILE) + add_library(${NAME} SHARED ${SRCFILE}) + set_target_properties(${NAME} PROPERTIES + RUNTIME_OUTPUT_DIRECTORY ${PLUGIN_OUTPUT_DIR} + LIBRARY_OUTPUT_DIRECTORY ${PLUGIN_OUTPUT_DIR} + ARCHIVE_OUTPUT_DIRECTORY ${PLUGIN_OUTPUT_DIR} + ) +endfunction() + +# ------------------------------ +# ������� (������ DLL) +# ------------------------------ +add_calc_plugin(funcsin plugins_src/funcsin.cpp) +add_calc_plugin(funcln plugins_src/funcln.cpp) + +# ------------------------------ +# ��������� ����� ������ +# ------------------------------ +message(STATUS "Plugins will be placed in: ${PLUGIN_OUTPUT_DIR}") diff --git a/plugins_src/funcln.cpp b/plugins_src/funcln.cpp new file mode 100644 index 0000000..7e73219 --- /dev/null +++ b/plugins_src/funcln.cpp @@ -0,0 +1,33 @@ +// funcln.cpp +// Êîìïèëèðîâàòü â DLL: cl /LD /std:c++17 funcln.cpp /Fe:funcln.dll +// Èëè: g++ -shared -o funcln.dll funcln.cpp -Wl,--out-implib,libfuncln.a + +#include +#include + +extern "C" { + + struct PluginDescriptor { + const char* name; + int arity; + double(__cdecl* func)(const double* args, int argc); + }; + + static double __cdecl ln_impl(const double* args, int argc) { + if (argc != 1) throw std::runtime_error("ln expects 1 argument"); + double x = args[0]; + if (x <= 0.0) throw std::runtime_error("ln domain error: argument must be > 0"); + return std::log(x); + } + + static PluginDescriptor descriptor = { + "ln", + 1, + ln_impl + }; + + __declspec(dllexport) PluginDescriptor* __cdecl register_plugin() { + return &descriptor; + } + +} // extern "C" diff --git a/plugins_src/funcsin.cpp b/plugins_src/funcsin.cpp new file mode 100644 index 0000000..6b5e784 --- /dev/null +++ b/plugins_src/funcsin.cpp @@ -0,0 +1,29 @@ +#include +#include + +extern "C" { + + struct PluginDescriptor { + const char* name; + int arity; + double(__cdecl* func)(const double*, int); + }; + + static double __cdecl sin_deg(const double* args, int argc) { + const double pi = std::acos(-1); + if (argc != 1) throw std::runtime_error("sin expects 1 argument"); + double radians = args[0] * pi / 180.0; + return std::sin(radians); + } + + static PluginDescriptor desc = { + "sin", + 1, + sin_deg + }; + + __declspec(dllexport) PluginDescriptor* __cdecl register_plugin() { + return &desc; + } + +} diff --git a/src/calculator.cpp b/src/calculator.cpp new file mode 100644 index 0000000..0744af0 --- /dev/null +++ b/src/calculator.cpp @@ -0,0 +1,146 @@ +#include "calculator.h" +#include +#include +#include +#include +#include + +using namespace std; + +Calculator::Calculator(const PluginManager& pm) : plugins(pm) {} + +Token Calculator::getNextToken() { + while (pos < input.size() && isspace(input[pos])) pos++; + if (pos >= input.size()) return { TokenKind::End, 0, "" }; + + char c = input[pos]; + + if (isdigit(c) || c == '.') { + size_t start = pos; + while (pos < input.size() && (isdigit(input[pos]) || input[pos] == '.')) pos++; + double val = stod(input.substr(start, pos - start)); + return { TokenKind::Number, val, "" }; + } + + pos++; + switch (c) { + case '+': return { TokenKind::Plus }; + case '-': return { TokenKind::Minus }; + case '*': return { TokenKind::Mul }; + case '/': return { TokenKind::Div }; + case '(': return { TokenKind::LParen }; + case ')': return { TokenKind::RParen }; + case ',': return { TokenKind::Comma }; + default: + if (isalpha(c)) { + size_t start = pos - 1; + while (pos < input.size() && (isalpha(input[pos]) || isdigit(input[pos]))) pos++; + string text = input.substr(start, pos - start); + return { TokenKind::Identifier, 0, text }; + } + throw runtime_error(string("Unknown character: ") + c); + } +} + +double Calculator::evaluate(const string& expr) { + input = expr; + pos = 0; + double res = parseExpression(); + + Token t = getNextToken(); + if (t.kind != TokenKind::End) + throw runtime_error("Unexpected characters at end of expression"); + + return res; +} + +double Calculator::parseExpression() { + double left = parseTerm(); + while (true) { + size_t backup = pos; + Token t = getNextToken(); + if (t.kind == TokenKind::Plus) + left += parseTerm(); + else if (t.kind == TokenKind::Minus) + left -= parseTerm(); + else { + pos = backup; + break; + } + } + return left; +} + +double Calculator::parseTerm() { + double left = parseFactor(); + while (true) { + size_t backup = pos; + Token t = getNextToken(); + if (t.kind == TokenKind::Mul) + left *= parseFactor(); + else if (t.kind == TokenKind::Div) { + double r = parseFactor(); + if (r == 0) throw runtime_error("Division by zero"); + left /= r; + } + else { + pos = backup; + break; + } + } + return left; +} + +double Calculator::parseFactor() { + return parsePrimary(); +} + +double Calculator::parsePrimary() { + Token t = getNextToken(); + + if (t.kind == TokenKind::Number) + return t.value; + + if (t.kind == TokenKind::Minus) + return -parsePrimary(); + + if (t.kind == TokenKind::LParen) { + double val = parseExpression(); + Token r = getNextToken(); + if (r.kind != TokenKind::RParen) + throw runtime_error("Missing ')'"); + return val; + } + + if (t.kind == TokenKind::Identifier) { + string fname = t.text; + transform(fname.begin(), fname.end(), fname.begin(), ::tolower); + + Token next = getNextToken(); + if (next.kind != TokenKind::LParen) + throw runtime_error("Expected '(' after function name"); + + vector args; + + size_t backup = pos; + Token lookahead = getNextToken(); + if (lookahead.kind == TokenKind::RParen) { + } + else { + pos = backup; + args.push_back(parseExpression()); + while (true) { + Token comma = getNextToken(); + if (comma.kind == TokenKind::RParen) + break; + if (comma.kind != TokenKind::Comma) + throw runtime_error("Expected ',' or ')'"); + args.push_back(parseExpression()); + } + } + + return plugins.callFunction(fname, args); + } + + throw runtime_error("Unexpected token in expression"); +} diff --git a/src/calculator.h b/src/calculator.h new file mode 100644 index 0000000..238a696 --- /dev/null +++ b/src/calculator.h @@ -0,0 +1,20 @@ +#pragma once +#include "plugin_manager.h" +#include "token.h" +#include + +class Calculator { +public: + Calculator(const PluginManager& pm); + double evaluate(const std::string& expr); +private: + const PluginManager& plugins; + size_t pos; + std::string input; + + Token getNextToken(); + double parseExpression(); + double parseTerm(); + double parseFactor(); + double parsePrimary(); +}; diff --git a/src/main.cpp b/src/main.cpp new file mode 100644 index 0000000..768f8c8 --- /dev/null +++ b/src/main.cpp @@ -0,0 +1,28 @@ +#include "plugin_manager.h" +#include "calculator.h" +#include +#include + +int main() { + PluginManager plugins; + plugins.loadPlugins("./plugins"); + plugins.listFunctions(); + + Calculator calc(plugins); + + std::string expr; + std::cout << "Enter expression (empty to quit):\n"; + while (true) { + std::cout << "> "; + std::getline(std::cin, expr); + if (expr.empty()) break; + + try { + double result = calc.evaluate(expr); + std::cout << result << "\n"; + } + catch (const std::exception& e) { + std::cerr << "Error: " << e.what() << "\n"; + } + } +} diff --git a/src/plugin_manager.cpp b/src/plugin_manager.cpp new file mode 100644 index 0000000..cca1674 --- /dev/null +++ b/src/plugin_manager.cpp @@ -0,0 +1,87 @@ +#include "plugin_manager.h" +#include +#include +#include +#include + +using namespace std; +namespace fs = std::filesystem; + +void PluginManager::loadPlugins(const string& directory) { + functions.clear(); + + if (!fs::exists(directory)) { + cerr << "Plugins folder not found: " << directory << endl; + return; + } + + for (auto& entry : fs::directory_iterator(directory)) { + if (entry.is_regular_file() && entry.path().extension() == ".dll") { + string path = entry.path().string(); + HMODULE h = LoadLibraryA(path.c_str()); + if (!h) { + cerr << "Failed to load plugin: " << path << endl; + continue; + } + + auto reg = (PluginDescriptor * (__cdecl*)())GetProcAddress(h, "register_plugin"); + if (!reg) { + cerr << "register_plugin not found in " << path << endl; + FreeLibrary(h); + continue; + } + + try { + PluginDescriptor* desc = reg(); + if (desc) { + string name = desc->name ? desc->name : ""; + transform(name.begin(), name.end(), name.begin(), ::tolower); + functions[name] = *desc; + cout << "Loaded plugin: " << name + << " (arity " << desc->arity << ")" << endl; + } + } + catch (const exception& e) { + cerr << "Error in plugin " << path << ": " << e.what() << endl; + FreeLibrary(h); + } + } + } + + if (functions.empty()) + cerr << "Warning: no plugins loaded from " << directory << endl; +} + +bool PluginManager::hasFunction(const string& name) const { + string key = name; + transform(key.begin(), key.end(), key.begin(), ::tolower); + return functions.find(key) != functions.end(); +} + +double PluginManager::callFunction(const string& name, + const vector& args) const { + string key = name; + transform(key.begin(), key.end(), key.begin(), ::tolower); + auto it = functions.find(key); + if (it == functions.end()) + throw runtime_error("Unknown function: " + name); + + const auto& f = it->second; + if ((int)args.size() != f.arity) + throw runtime_error("Function '" + name + "' expects " + + to_string(f.arity) + " arguments"); + + try { + return f.func(args.data(), (int)args.size()); + } + catch (const exception& e) { + throw runtime_error("Error in function '" + name + "': " + e.what()); + } +} + +void PluginManager::listFunctions() const { + cout << "Available functions: "; + for (auto& [name, _] : functions) + cout << name << " "; + cout << endl; +} diff --git a/src/plugin_manager.h b/src/plugin_manager.h new file mode 100644 index 0000000..a012305 --- /dev/null +++ b/src/plugin_manager.h @@ -0,0 +1,21 @@ +#pragma once +#include +#include +#include +#include + +struct PluginDescriptor { + const char* name; + int arity; + double(__cdecl* func)(const double* args, int argc); +}; + +class PluginManager { +public: + void loadPlugins(const std::string& directory); + bool hasFunction(const std::string& name) const; + double callFunction(const std::string& name, const std::vector& args) const; + void listFunctions() const; +private: + std::unordered_map functions; +}; diff --git a/src/token.h b/src/token.h new file mode 100644 index 0000000..a516c1d --- /dev/null +++ b/src/token.h @@ -0,0 +1,17 @@ +#pragma once +#include + +enum class TokenKind { + Number, + Plus, Minus, Mul, Div, + LParen, RParen, + Comma, + Identifier, + End +}; + +struct Token { + TokenKind kind; + double value; + std::string text; +};