diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..b6e7e79 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,90 @@ +cmake_minimum_required(VERSION 3.16) +project(DynamicCalculator LANGUAGES CXX) + + +set(CMAKE_CXX_STANDARD 20) +set(CMAKE_CXX_STANDARD_REQUIRED ON) +set(CMAKE_CXX_EXTENSIONS OFF) + + +set(CMAKE_RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}") + + +set(PROJECT_INCLUDE_DIR "${CMAKE_SOURCE_DIR}/include") + + +add_executable(calc + src/main.cpp + src/tokenizer.cpp + src/parser.cpp + src/eval.cpp + src/plugins.cpp +) + +target_include_directories(calc PRIVATE "${PROJECT_INCLUDE_DIR}") + + +if(UNIX AND NOT APPLE) + target_link_libraries(calc PRIVATE dl) +endif() + + +foreach(cfg IN ITEMS Debug Release RelWithDebInfo MinSizeRel) + set_target_properties(calc PROPERTIES RUNTIME_OUTPUT_DIRECTORY_${cfg} "${CMAKE_RUNTIME_OUTPUT_DIRECTORY}") +endforeach() + + +set(PLUGINS_OUT "${CMAKE_BINARY_DIR}/plugins") +file(MAKE_DIRECTORY "${PLUGINS_OUT}") + +function(configure_plugin target source) + add_library(${target} SHARED ${source}) + target_include_directories(${target} PRIVATE "${PROJECT_INCLUDE_DIR}") + + + set_target_properties(${target} PROPERTIES + PREFIX "" + RUNTIME_OUTPUT_DIRECTORY "${PLUGINS_OUT}" + LIBRARY_OUTPUT_DIRECTORY "${PLUGINS_OUT}" + ARCHIVE_OUTPUT_DIRECTORY "${PLUGINS_OUT}" + ) + + + foreach(cfg IN ITEMS Debug Release RelWithDebInfo MinSizeRel) + set_target_properties(${target} PROPERTIES + RUNTIME_OUTPUT_DIRECTORY_${cfg} "${PLUGINS_OUT}" + LIBRARY_OUTPUT_DIRECTORY_${cfg} "${PLUGINS_OUT}" + ARCHIVE_OUTPUT_DIRECTORY_${cfg} "${PLUGINS_OUT}" + ) + endforeach() + + + if(WIN32) + set_target_properties(${target} PROPERTIES SUFFIX ".dll") + elseif(APPLE) + set_target_properties(${target} PROPERTIES SUFFIX ".dylib") + else() + set_target_properties(${target} PROPERTIES SUFFIX ".so") + endif() + + + add_dependencies(calc ${target}) +endfunction() + + +configure_plugin(funcsin "${CMAKE_SOURCE_DIR}/plugins/funcsin.cpp") +configure_plugin(funccos "${CMAKE_SOURCE_DIR}/plugins/funccos.cpp") +configure_plugin(funcsqrt "${CMAKE_SOURCE_DIR}/plugins/funcsqrt.cpp") +configure_plugin(funcln "${CMAKE_SOURCE_DIR}/plugins/funcln.cpp") + + +option(ENABLE_ASAN "Enable AddressSanitizer for debug builds" OFF) +if(ENABLE_ASAN AND (CMAKE_CXX_COMPILER_ID MATCHES "Clang|AppleClang|GNU")) + message(STATUS "AddressSanitizer enabled") + target_compile_options(calc PRIVATE -fsanitize=address -fno-omit-frame-pointer) + target_link_options(calc PRIVATE -fsanitize=address) + foreach(p funcsin funccos funcsqrt funcln) + target_compile_options(${p} PRIVATE -fsanitize=address -fno-omit-frame-pointer) + target_link_options(${p} PRIVATE -fsanitize=address) + endforeach() +endif() diff --git a/include/calc/eval.hpp b/include/calc/eval.hpp new file mode 100644 index 0000000..8c0eefa --- /dev/null +++ b/include/calc/eval.hpp @@ -0,0 +1,60 @@ +#pragma once +#include "calc/tokenizer.hpp" +#include "calc/parser.hpp" +#include "calc/plugins.hpp" +#include +#include +#include +#include + +namespace calc { + + struct ICommand { + virtual ~ICommand() = default; + virtual void execute(std::vector& stack) const = 0; + }; + + class PushLiteralCommand : public ICommand { + public: + explicit PushLiteralCommand(double v); + void execute(std::vector& stack) const override; + private: + double v_; + }; + + class UnaryCommand : public ICommand { + public: + explicit UnaryCommand(std::function f); + void execute(std::vector& stack) const override; + private: + std::function f_; + }; + + class BinaryCommand : public ICommand { + public: + explicit BinaryCommand(std::function f); + void execute(std::vector& stack) const override; + private: + std::function f_; + }; + + class CommandBuilder { + public: + CommandBuilder(const FunctionRegistry& r, const OperatorTable& o); + std::vector> build(const std::vector& rpn) const; + private: + const FunctionRegistry& reg_; + const OperatorTable& ops_; + }; + + class Calculator { + public: + explicit Calculator(FunctionRegistry r); + double evaluate(const std::string& expr) const; + void print_functions(std::ostream& os) const; + + private: + FunctionRegistry reg_; + }; + +} // namespace calc diff --git a/include/calc/parser.hpp b/include/calc/parser.hpp new file mode 100644 index 0000000..d72209d --- /dev/null +++ b/include/calc/parser.hpp @@ -0,0 +1,35 @@ +#pragma once +#include "calc/tokenizer.hpp" +#include +#include +#include +#include +#include + +namespace calc { + + struct OperatorInfo { + int precedence; + bool right_assoc; + int arity; + std::function binary; + std::function unary; + }; + + class OperatorTable { + public: + OperatorTable(); + const OperatorInfo& get(const std::string& k) const; + bool contains(const std::string& k) const; + private: + std::map tbl; + }; + + class RpnCompiler { + public: + std::vector compile(const std::vector& tokens); + private: + static bool is_unary(const std::optional& prev, const Token& tok); + }; + +} // namespace calc diff --git a/include/calc/plugin_api.h b/include/calc/plugin_api.h new file mode 100644 index 0000000..df711a0 --- /dev/null +++ b/include/calc/plugin_api.h @@ -0,0 +1,29 @@ +#pragma once +#include + +namespace calc::plugin { + +#if defined(_WIN32) +#define CALC_API extern "C" __declspec(dllexport) +#define CALC_CALL __cdecl +#else +#define CALC_API extern "C" +#define CALC_CALL +#endif + + using InvokeFn = double (CALC_CALL *)(double); + + struct FunctionDescriptor { + std::uint32_t abi_version; + const char* name; + InvokeFn invoke; + }; + + using RegisterFunction = bool (CALC_CALL *)(FunctionDescriptor&); + + inline constexpr std::uint32_t kAbiVersion = 1; + inline constexpr const char* kRegistrationSymbol = "calc_register"; + + static_assert(sizeof(InvokeFn) == sizeof(void*), "InvokeFn must be a plain function pointer"); + +} // namespace calc::plugin diff --git a/include/calc/plugins.hpp b/include/calc/plugins.hpp new file mode 100644 index 0000000..374d013 --- /dev/null +++ b/include/calc/plugins.hpp @@ -0,0 +1,64 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#include + +namespace calc { + + namespace fs = std::filesystem; + + class DynamicLibrary { + public: + explicit DynamicLibrary(const fs::path& path); + ~DynamicLibrary(); + + DynamicLibrary(const DynamicLibrary&) = delete; + DynamicLibrary& operator=(const DynamicLibrary&) = delete; + + DynamicLibrary(DynamicLibrary&& other) noexcept; + DynamicLibrary& operator=(DynamicLibrary&& other) noexcept; + + + void* symbol(const char* name) const; + + private: + fs::path path_; + void* handle_ = nullptr; + + void swap(DynamicLibrary& other) noexcept; + }; + + // Реестр функций-плагинов + class FunctionRegistry { + public: + using UnaryFunction = std::function; + + void add_function(std::string name, UnaryFunction fn); + bool contains(const std::string& name) const; + UnaryFunction get_function(const std::string& name) const; + bool empty() const noexcept; + std::vector names() const; + + private: + std::map functions_; + + static std::string normalize(std::string s); + }; + + class PluginLoader { + public: + explicit PluginLoader(fs::path dir); + + void load(FunctionRegistry& registry); + + private: + fs::path dir_; + std::vector> libs_; + }; + +} diff --git a/include/calc/tokenizer.hpp b/include/calc/tokenizer.hpp new file mode 100644 index 0000000..2a2b790 --- /dev/null +++ b/include/calc/tokenizer.hpp @@ -0,0 +1,34 @@ +#pragma once +#include +#include +#include + +namespace calc { + + enum class TokenType { + Number, + Operator, + Function, + LeftParen, + RightParen + }; + + struct Token { + TokenType type; + std::string text; + double number{}; + }; + + class Tokenizer { + public: + explicit Tokenizer(std::string s); + std::vector tokenize() const; + + private: + std::pair parse_number(std::size_t start) const; + std::pair parse_id(std::size_t start) const; + + std::string s_; + }; + +} // namespace calc diff --git a/plugins/funccos.cpp b/plugins/funccos.cpp new file mode 100644 index 0000000..6fdb221 --- /dev/null +++ b/plugins/funccos.cpp @@ -0,0 +1,17 @@ +#include "calc/plugin_api.h" +#include +using namespace calc::plugin; + + +static double CALC_CALL f(double x_deg) { + constexpr long double PI = 3.141592653589793238462643383279502884L; + long double rad = (long double)x_deg * (PI / 180.0L); + return std::cos((double)rad); +} + +CALC_API bool CALC_CALL calc_register(FunctionDescriptor& d) { + d.abi_version = kAbiVersion; + d.name = "cos"; + d.invoke = &f; + return true; +} diff --git a/plugins/funcln.cpp b/plugins/funcln.cpp new file mode 100644 index 0000000..1357e09 --- /dev/null +++ b/plugins/funcln.cpp @@ -0,0 +1,16 @@ +#include "calc/plugin_api.h" +#include +#include +using namespace calc::plugin; + +static double CALC_CALL f(double x) { + if (x <= 0) throw std::domain_error("ln: x <= 0"); + return std::log(x); +} + +CALC_API bool CALC_CALL calc_register(FunctionDescriptor& d) { + d.abi_version = kAbiVersion; + d.name = "ln"; + d.invoke = &f; + return true; +} diff --git a/plugins/funcsin.cpp b/plugins/funcsin.cpp new file mode 100644 index 0000000..729eb28 --- /dev/null +++ b/plugins/funcsin.cpp @@ -0,0 +1,17 @@ +#include "calc/plugin_api.h" +#include +using namespace calc::plugin; + + +static double CALC_CALL f(double x_deg) { + constexpr long double PI = 3.141592653589793238462643383279502884L; + long double rad = (long double)x_deg * (PI / 180.0L); + return std::sin((double)rad); +} + +CALC_API bool CALC_CALL calc_register(FunctionDescriptor& d) { + d.abi_version = kAbiVersion; + d.name = "sin"; + d.invoke = &f; + return true; +} diff --git a/plugins/funcsqrt.cpp b/plugins/funcsqrt.cpp new file mode 100644 index 0000000..3b68e11 --- /dev/null +++ b/plugins/funcsqrt.cpp @@ -0,0 +1,16 @@ +#include "calc/plugin_api.h" +#include +#include +using namespace calc::plugin; + +static double CALC_CALL f(double x) { + if (x < 0) throw std::domain_error("sqrt: x < 0"); + return std::sqrt(x); +} + +CALC_API bool CALC_CALL calc_register(FunctionDescriptor& d) { + d.abi_version = kAbiVersion; + d.name = "sqrt"; + d.invoke = &f; + return true; +} diff --git a/src/eval.cpp b/src/eval.cpp new file mode 100644 index 0000000..66bdbf8 --- /dev/null +++ b/src/eval.cpp @@ -0,0 +1,78 @@ +#include "calc/eval.hpp" +#include "calc/tokenizer.hpp" + +namespace calc { + +PushLiteralCommand::PushLiteralCommand(double v) : v_(v) {} +void PushLiteralCommand::execute(std::vector& stack) const { stack.push_back(v_); } + +UnaryCommand::UnaryCommand(std::function f) : f_(std::move(f)) {} +void UnaryCommand::execute(std::vector& stack) const { + if (stack.empty()) throw std::runtime_error("Не хватает операндов"); + double a = stack.back(); stack.pop_back(); + stack.push_back(f_(a)); +} + +BinaryCommand::BinaryCommand(std::function f) : f_(std::move(f)) {} +void BinaryCommand::execute(std::vector& stack) const { + if (stack.size() < 2) throw std::runtime_error("Не хватает операндов"); + double b = stack.back(); stack.pop_back(); + double a = stack.back(); stack.pop_back(); + stack.push_back(f_(a,b)); +} + +CommandBuilder::CommandBuilder(const FunctionRegistry& r, const OperatorTable& o) : reg_(r), ops_(o) {} + +std::vector> CommandBuilder::build(const std::vector& rpn) const { + std::vector> cmds; + cmds.reserve(rpn.size()); + for (const auto& t : rpn) { + switch (t.type) { + case TokenType::Number: + cmds.push_back(std::make_unique(t.number)); + break; + case TokenType::Operator: { + const auto& op = ops_.get(t.text); + if (op.arity == 1) cmds.push_back(std::make_unique(op.unary)); + else if (op.arity == 2) cmds.push_back(std::make_unique(op.binary)); + else throw std::runtime_error("Неподдерживаемая арность оператора"); + break; + } + case TokenType::Function: { + auto fn = reg_.get_function(t.text); + cmds.push_back(std::make_unique(std::move(fn))); + break; + } + default: + throw std::runtime_error("Неожиданный токен"); + } + } + return cmds; +} + +Calculator::Calculator(FunctionRegistry r) : reg_(std::move(r)) {} + +double Calculator::evaluate(const std::string& expr) const { + Tokenizer tz(expr); + auto tokens = tz.tokenize(); + OperatorTable ops; + RpnCompiler cp; + auto rpn = cp.compile(tokens); + CommandBuilder bld(reg_, ops); + auto cmds = bld.build(rpn); + std::vector st; + st.reserve(cmds.size()); + for (auto& c : cmds) c->execute(st); + if (st.size() != 1) throw std::runtime_error("Неверное выражение"); + return st.front(); +} + +void Calculator::print_functions(std::ostream& os) const { + if (reg_.empty()) { os << " Плагиновые функции не загружены\n"; return; } + os << " Доступные функции: "; + auto n = reg_.names(); + for (std::size_t i = 0; i < n.size(); ++i) { if (i) os << ", "; os << n[i]; } + os << '\n'; +} + +} // namespace calc diff --git a/src/main.cpp b/src/main.cpp new file mode 100644 index 0000000..7735d34 --- /dev/null +++ b/src/main.cpp @@ -0,0 +1,111 @@ +#include +#include +#include +#include +#include +#include + +#if defined(_WIN32) + #define NOMINMAX + #include +#endif + +#include "calc/eval.hpp" +#include "calc/plugins.hpp" + +namespace fs = std::filesystem; + +static const char* platform_ext() { +#if defined(_WIN32) + return ".dll"; +#elif defined(__APPLE__) + return ".dylib"; +#else + return ".so"; +#endif +} + +int main(int argc, char* argv[]) { + try { +#if defined(_WIN32) + SetConsoleOutputCP(CP_UTF8); + SetConsoleCP(CP_UTF8); +#endif + + calc::FunctionRegistry reg; + +#if defined(_DEBUG) + const char* cfg = "Debug"; +#else + const char* cfg = "Release"; +#endif + + std::vector dirs; + + fs::path cwd = fs::current_path(); + dirs.push_back(cwd / "plugins"); + dirs.push_back(cwd / "plugins" / cfg); + + if (argc > 0 && argv && argv[0]) { + try { + fs::path exe = fs::weakly_canonical(fs::path(argv[0])); + fs::path exe_dir = exe.parent_path(); + + dirs.push_back(exe_dir / "plugins"); + dirs.push_back(exe_dir / "plugins" / cfg); + + dirs.push_back(exe_dir.parent_path() / "plugins"); + dirs.push_back(exe_dir.parent_path() / "plugins" / cfg); + } catch (...) { + } + } + + for (auto& d : dirs) { + try { d = fs::weakly_canonical(d); } catch (...) {} + } + std::sort(dirs.begin(), dirs.end()); + dirs.erase(std::unique(dirs.begin(), dirs.end()), dirs.end()); + + std::vector> keep_alive; + bool any_loaded = false; + std::optional last_err; + + for (const auto& d : dirs) { + try { + auto loader = std::make_unique(d); + loader->load(reg); + keep_alive.push_back(std::move(loader)); + any_loaded = true; + break; + } catch (const std::exception& ex) { + last_err = ex.what(); + } + } + if (!any_loaded && last_err) { + std::cerr << "[Внимание] " << *last_err << '\n'; + } + + calc::Calculator calc(std::move(reg)); + std::cout << "Калькулятор (динамические плагины " << platform_ext() << ")\n"; + calc.print_functions(std::cout); + std::cout << "Пример: 16 + 4 * (3 - 1) -> 24\n"; + std::cout << "Введите выражение. Пустая строка — выход.\n"; + + std::string line; + while (true) { + std::cout << "> "; + if (!std::getline(std::cin, line)) break; + if (line.empty()) break; + try { + double r = calc.evaluate(line); + std::cout << r << '\n'; + } catch (const std::exception& ex) { + std::cout << "Ошибка: " << ex.what() << '\n'; + } + } + return 0; + } catch (const std::exception& ex) { + std::cerr << "Критическая ошибка: " << ex.what() << '\n'; + return 1; + } +} diff --git a/src/parser.cpp b/src/parser.cpp new file mode 100644 index 0000000..b5bcb64 --- /dev/null +++ b/src/parser.cpp @@ -0,0 +1,91 @@ +#include "calc/parser.hpp" +#include +#include +#include + +namespace calc { + +OperatorTable::OperatorTable() { + tbl.emplace("+", OperatorInfo{10, false, 2, [](double a,double b){return a+b;}, {}}); + tbl.emplace("-", OperatorInfo{10, false, 2, [](double a,double b){return a-b;}, {}}); + tbl.emplace("*", OperatorInfo{20, false, 2, [](double a,double b){return a*b;}, {}}); + tbl.emplace("/", OperatorInfo{20, false, 2, [](double a,double b){ if (b==0.0) throw std::runtime_error("Деление на ноль"); return a/b; }, {}}); + tbl.emplace("^", OperatorInfo{30, true , 2, [](double a,double b){return std::pow(a,b);}, {}}); + tbl.emplace("u-",OperatorInfo{40, true , 1, {}, [](double x){return -x;}}); + tbl.emplace("u+",OperatorInfo{40, true , 1, {}, [](double x){return x;}}); +} + +const OperatorInfo& OperatorTable::get(const std::string& k) const { + auto it = tbl.find(k); + if (it == tbl.end()) throw std::runtime_error("Неизвестный оператор: " + k); + return it->second; +} +bool OperatorTable::contains(const std::string& k) const { return tbl.count(k) != 0; } + +std::vector RpnCompiler::compile(const std::vector& tokens) { + std::vector out; + std::vector ops; + const OperatorTable opt; + std::optional prev; + for (std::size_t i = 0; i < tokens.size(); ++i) { + Token tok = tokens[i]; + switch (tok.type) { + case TokenType::Number: + out.push_back(tok); prev = tok; break; + case TokenType::Function: + ops.push_back(tok); prev = tok; break; + case TokenType::Operator: { + std::string key = tok.text; + if (is_unary(prev, tok)) { key = tok.text == "-" ? "u-" : "u+"; tok.text = key; } + const auto& cur = opt.get(key); + while (!ops.empty()) { + const Token& top = ops.back(); + if (top.type == TokenType::Function) { out.push_back(top); ops.pop_back(); continue; } + if (top.type == TokenType::Operator) { + const auto& ti = opt.get(top.text); + bool higher = (!cur.right_assoc && ti.precedence >= cur.precedence) || + ( cur.right_assoc && ti.precedence > cur.precedence); + if (higher) { out.push_back(top); ops.pop_back(); continue; } + } + break; + } + ops.push_back(tok); prev = tok; break; + } + case TokenType::LeftParen: + ops.push_back(tok); prev = tok; break; + case TokenType::RightParen: { + bool found = false; + while (!ops.empty()) { + Token top = ops.back(); ops.pop_back(); + if (top.type == TokenType::LeftParen) { found = true; break; } + out.push_back(top); + } + if (!found) throw std::runtime_error("Скобки не совпадают"); + if (!ops.empty() && ops.back().type == TokenType::Function) { + out.push_back(ops.back()); ops.pop_back(); + } + prev = tok; break; + } + } + } + while (!ops.empty()) { + Token top = ops.back(); ops.pop_back(); + if (top.type == TokenType::LeftParen || top.type == TokenType::RightParen) throw std::runtime_error("Скобки не совпадают"); + out.push_back(top); + } + return out; +} + +bool RpnCompiler::is_unary(const std::optional& prev, const Token& tok) { + if (tok.text != "-" && tok.text != "+") return false; + if (!prev.has_value()) return true; + switch (prev->type) { + case TokenType::Operator: + case TokenType::LeftParen: + return true; + default: + return false; + } +} + +} // namespace calc diff --git a/src/plugins.cpp b/src/plugins.cpp new file mode 100644 index 0000000..c6011d3 --- /dev/null +++ b/src/plugins.cpp @@ -0,0 +1,220 @@ +#include "calc/plugins.hpp" +#include +#include +#include + +#if defined(_WIN32) + #define NOMINMAX + #include +#else + #include +#endif + +#include "calc/plugin_api.h" + +namespace calc { + + +DynamicLibrary::DynamicLibrary(const fs::path& path) : path_(path) { +#if defined(_WIN32) + handle_ = LoadLibraryW(path.wstring().c_str()); + if (!handle_) { + throw std::runtime_error("Не удалось загрузить библиотеку: " + path.string()); + } +#else + handle_ = dlopen(path.c_str(), RTLD_NOW | RTLD_LOCAL); + if (!handle_) { + throw std::runtime_error( + std::string("Не удалось загрузить библиотеку ") + + path.string() + ": " + dlerror()); + } +#endif +} + +DynamicLibrary::~DynamicLibrary() { +#if defined(_WIN32) + if (handle_) { + FreeLibrary((HMODULE)handle_); + } +#else + if (handle_) { + dlclose(handle_); + } +#endif +} + +DynamicLibrary::DynamicLibrary(DynamicLibrary&& other) noexcept { + swap(other); +} + +DynamicLibrary& DynamicLibrary::operator=(DynamicLibrary&& other) noexcept { + if (this != &other) { + DynamicLibrary tmp(std::move(other)); + swap(tmp); + } + return *this; +} + +void* DynamicLibrary::symbol(const char* name) const { +#if defined(_WIN32) + auto s = GetProcAddress((HMODULE)handle_, name); + if (!s) { + throw std::runtime_error("Не найден символ: " + std::string(name)); + } + return reinterpret_cast(s); +#else + dlerror(); + void* s = dlsym(handle_, name); + if (const char* e = dlerror()) { + throw std::runtime_error(std::string("Ошибка поиска символа: ") + e); + } + if (!s) { + throw std::runtime_error("Пустой символ: " + std::string(name)); + } + return s; +#endif +} + +void DynamicLibrary::swap(DynamicLibrary& other) noexcept { + std::swap(path_, other.path_); + std::swap(handle_, other.handle_); +} + + +void FunctionRegistry::add_function(std::string name, UnaryFunction fn) { + auto n = normalize(std::move(name)); + if (functions_.count(n)) { + throw std::runtime_error("Дублирование функции: " + n); + } + functions_.emplace(std::move(n), std::move(fn)); +} + +bool FunctionRegistry::contains(const std::string& name) const { + return functions_.count(normalize(name)) != 0; +} + +FunctionRegistry::UnaryFunction +FunctionRegistry::get_function(const std::string& name) const { + auto it = functions_.find(normalize(name)); + if (it == functions_.end()) { + throw std::runtime_error("Неизвестная функция: " + name); + } + return it->second; +} + +bool FunctionRegistry::empty() const noexcept { + return functions_.empty(); +} + +std::vector FunctionRegistry::names() const { + std::vector v; + v.reserve(functions_.size()); + for (auto& [k, _] : functions_) { + v.push_back(k); + } + std::sort(v.begin(), v.end()); + return v; +} + +std::string FunctionRegistry::normalize(std::string s) { + std::transform( + s.begin(), s.end(), s.begin(), + [](unsigned char c) { return char(std::tolower(c)); }); + return s; +} + + +PluginLoader::PluginLoader(fs::path dir) : dir_(std::move(dir)) {} + +void PluginLoader::load(FunctionRegistry& registry) { + if (!fs::exists(dir_)) { + throw std::runtime_error("Нет директории с плагинами: " + dir_.string()); + } + + std::size_t total = 0, matched = 0, registered = 0; + + for (const auto& e : fs::directory_iterator(dir_)) { + if (!e.is_regular_file()) { + continue; + } + ++total; + const auto& p = e.path(); + + bool ok = false; +#if defined(_WIN32) + ok = (p.extension() == ".dll"); +#elif defined(__APPLE__) + ok = (p.extension() == ".dylib"); +#else + ok = (p.extension() == ".so"); +#endif + if (!ok) { + continue; + } + ++matched; + + try { + auto lib = std::make_unique(p); + auto* regfn = reinterpret_cast( + lib->symbol(calc::plugin::kRegistrationSymbol)); + if (!regfn) { + } + + calc::plugin::FunctionDescriptor d{}; + if (!regfn(d)) { + throw std::runtime_error("Плагин вернул неуспех"); + } + if (d.abi_version != calc::plugin::kAbiVersion) { + throw std::runtime_error("Несовместимая версия ABI"); + } + if (!d.name || !d.invoke) { + throw std::runtime_error("Плагин вернул пустой дескриптор"); + } + + if (registry.contains(d.name)) { + continue; + } + + registry.add_function( + d.name, + [fn = d.invoke, id = std::string(d.name)](double x) { + try { + return fn(x); + } catch (const std::exception& ex) { + throw std::runtime_error( + "Ошибка функции '" + id + "': " + ex.what()); + } catch (...) { + throw std::runtime_error( + "Неизвестная ошибка функции '" + id + "'"); + } + }); + + libs_.push_back(std::move(lib)); + ++registered; + } catch (...) { + } + } + +#if defined(_WIN32) + const char* need = ".dll"; +#elif defined(__APPLE__) + const char* need = ".dylib"; +#else + const char* need = ".so"; +#endif + + if (total == 0) { + throw std::runtime_error( + std::string("Каталог пуст. Ожидались плагины с расширением ") + need); + } + if (matched == 0) { + throw std::runtime_error( + std::string("Нет файлов плагинов с расширением ") + need); + } + if (registered == 0) { + throw std::runtime_error( + "Файлы плагинов найдены, но ни одна функция не зарегистрирована"); + } +} + +} diff --git a/src/tokenizer.cpp b/src/tokenizer.cpp new file mode 100644 index 0000000..8a24801 --- /dev/null +++ b/src/tokenizer.cpp @@ -0,0 +1,77 @@ +#include "calc/tokenizer.hpp" +#include +#include +#include + +namespace calc { + +Tokenizer::Tokenizer(std::string s) : s_(std::move(s)) {} + +std::vector Tokenizer::tokenize() const { + std::vector t; + std::size_t i = 0; + while (i < s_.size()) { + char ch = s_[i]; + + if (std::isspace((unsigned char)ch)) { ++i; continue; } + + if (std::isdigit((unsigned char)ch) || ch == '.') { + auto [num, next] = parse_number(i); + t.push_back(Token{TokenType::Number, "", num}); + i = next; continue; + } + + if (std::isalpha((unsigned char)ch)) { + auto [id, next] = parse_id(i); + std::string name = id; + std::transform(name.begin(), name.end(), name.begin(), + [](unsigned char c){ return char(std::tolower(c)); }); + + std::size_t j = next; + while (j < s_.size() && std::isspace((unsigned char)s_[j])) ++j; + if (j < s_.size() && s_[j] == '(') { + t.push_back(Token{TokenType::Function, name, 0.0}); + } else { + throw std::runtime_error("Ожидался вызов функции: " + name + "(...)"); + } + i = next; continue; + } + + switch (ch) { + case '+': case '-': case '*': case '/': case '^': + t.push_back(Token{TokenType::Operator, std::string(1, ch), 0.0}); ++i; break; + case '(': + t.push_back(Token{TokenType::LeftParen, "(", 0.0}); ++i; break; + case ')': + t.push_back(Token{TokenType::RightParen, ")", 0.0}); ++i; break; + default: + throw std::runtime_error(std::string("Неожиданный символ: ") + ch); + } + } + return t; +} + +std::pair Tokenizer::parse_number(std::size_t start) const { + std::size_t pos = start; bool dot = false; + while (pos < s_.size()) { + char ch = s_[pos]; + if (std::isdigit((unsigned char)ch)) { ++pos; continue; } + if (ch == '.' && !dot) { dot = true; ++pos; continue; } + break; + } + std::string v = s_.substr(start, pos - start); + if (v.empty() || v == ".") throw std::runtime_error("Некорректное число"); + return {std::stod(v), pos}; +} + +std::pair Tokenizer::parse_id(std::size_t start) const { + std::size_t pos = start; + while (pos < s_.size()) { + char ch = s_[pos]; + if (std::isalnum((unsigned char)ch) || ch == '_') { ++pos; continue; } + break; + } + return {s_.substr(start, pos - start), pos}; +} + +} // namespace calc