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
90 changes: 90 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -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()
60 changes: 60 additions & 0 deletions include/calc/eval.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
#pragma once
#include "calc/tokenizer.hpp"
#include "calc/parser.hpp"
#include "calc/plugins.hpp"
#include <functional>
#include <memory>
#include <vector>
#include <ostream>

namespace calc {

struct ICommand {
virtual ~ICommand() = default;
virtual void execute(std::vector<double>& stack) const = 0;
};

class PushLiteralCommand : public ICommand {
public:
explicit PushLiteralCommand(double v);
void execute(std::vector<double>& stack) const override;
private:
double v_;
};

class UnaryCommand : public ICommand {
public:
explicit UnaryCommand(std::function<double(double)> f);
void execute(std::vector<double>& stack) const override;
private:
std::function<double(double)> f_;
};

class BinaryCommand : public ICommand {
public:
explicit BinaryCommand(std::function<double(double,double)> f);
void execute(std::vector<double>& stack) const override;
private:
std::function<double(double,double)> f_;
};

class CommandBuilder {
public:
CommandBuilder(const FunctionRegistry& r, const OperatorTable& o);
std::vector<std::unique_ptr<ICommand>> build(const std::vector<Token>& 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
35 changes: 35 additions & 0 deletions include/calc/parser.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
#pragma once
#include "calc/tokenizer.hpp"
#include <functional>
#include <optional>
#include <map>
#include <string>
#include <vector>

namespace calc {

struct OperatorInfo {
int precedence;
bool right_assoc;
int arity;
std::function<double(double,double)> binary;
std::function<double(double)> unary;
};

class OperatorTable {
public:
OperatorTable();
const OperatorInfo& get(const std::string& k) const;
bool contains(const std::string& k) const;
private:
std::map<std::string, OperatorInfo> tbl;
};

class RpnCompiler {
public:
std::vector<Token> compile(const std::vector<Token>& tokens);
private:
static bool is_unary(const std::optional<Token>& prev, const Token& tok);
};

} // namespace calc
29 changes: 29 additions & 0 deletions include/calc/plugin_api.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
#pragma once
#include <cstdint>

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
64 changes: 64 additions & 0 deletions include/calc/plugins.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
#pragma once

#include <filesystem>
#include <functional>
#include <map>
#include <memory>
#include <string>
#include <vector>
#include <algorithm>

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<double(double)>;

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<std::string> names() const;

private:
std::map<std::string, UnaryFunction> 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<std::unique_ptr<DynamicLibrary>> libs_;
};

}
34 changes: 34 additions & 0 deletions include/calc/tokenizer.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
#pragma once
#include <string>
#include <vector>
#include <utility>

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<Token> tokenize() const;

private:
std::pair<double, std::size_t> parse_number(std::size_t start) const;
std::pair<std::string, std::size_t> parse_id(std::size_t start) const;

std::string s_;
};

} // namespace calc
17 changes: 17 additions & 0 deletions plugins/funccos.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
#include "calc/plugin_api.h"
#include <cmath>
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;
}
16 changes: 16 additions & 0 deletions plugins/funcln.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
#include "calc/plugin_api.h"
#include <cmath>
#include <stdexcept>
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;
}
17 changes: 17 additions & 0 deletions plugins/funcsin.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
#include "calc/plugin_api.h"
#include <cmath>
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;
}
16 changes: 16 additions & 0 deletions plugins/funcsqrt.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
#include "calc/plugin_api.h"
#include <cmath>
#include <stdexcept>
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;
}
Loading