Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
5f56516
pluginManager done
Fromant Oct 15, 2025
ec3487e
error thrown from plugin now contain function name
Fromant Oct 15, 2025
ce2b26b
plugin structure documentation
Fromant Oct 15, 2025
90b8407
plugin example and structure clarification
Fromant Oct 15, 2025
74dbc68
cmake to build all plugins
Fromant Oct 18, 2025
820dc02
calculator frontend (tokenizer)
Fromant Oct 18, 2025
12621e0
Huge plugins rework: add custom operators support
Fromant Oct 20, 2025
ee3864c
Plugin manager add checks for plugin name and duplicating plugins
Fromant Oct 25, 2025
aa2bb77
make validatePlugin() static
Fromant Oct 25, 2025
006ce29
make validatePlugin() static
Fromant Oct 25, 2025
d2abc46
add shunting yard algorithm to transform arithmetical notation to pos…
Fromant Oct 25, 2025
0dc2d5d
add evaluating stack machine for rpn expressions
Fromant Oct 25, 2025
b74c3b4
add calculator class that encapsulates all logic
Fromant Oct 25, 2025
2757ad5
add main function and cmake config
Fromant Oct 25, 2025
55262bb
add +-*/^ operator plugins
Fromant Oct 25, 2025
1c9cef7
fix outputting .dll.a static version of each plugin
Fromant Oct 25, 2025
8eb5dfe
fix size_t not included
Fromant Oct 25, 2025
6149563
make separate cmake projects for plugins, testing, and core library c…
Fromant Oct 25, 2025
ab2c5be
make IPluginRegistry interface
Fromant Oct 25, 2025
5ea6bd0
add simple unit tests
Fromant Oct 25, 2025
3b483ba
made calculator platform independent
Fromant Oct 26, 2025
66c3564
fix: required plugin extension is now also platform independent
Fromant Oct 26, 2025
02228bc
add readme
Fromant Nov 22, 2025
b2f5622
plugins rework
Fromant Nov 22, 2025
1224783
add cos
Fromant Nov 22, 2025
a0379fa
add ln
Fromant Nov 22, 2025
90f430d
add tg
Fromant Nov 22, 2025
e6673eb
add unary operators support
Fromant Nov 23, 2025
53f8a5e
increase pow precedence
Fromant Nov 23, 2025
fc07ae4
ban functions without parentheses
Fromant Nov 23, 2025
e48a359
update README.md
Fromant Nov 23, 2025
8c956c3
update tests
Fromant Nov 23, 2025
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
30 changes: 30 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
cmake_minimum_required(VERSION 3.14)
project(CalcApp LANGUAGES CXX)

set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)

add_subdirectory(src)

add_executable(CalcApp main.cpp)

target_link_libraries(CalcApp CalcLib)

# Build plugins
add_subdirectory(plugins)

# Post-build: copy DLLs to ./plugins next to calc.exe
add_custom_command(TARGET CalcApp POST_BUILD
COMMAND ${CMAKE_COMMAND} -E make_directory $<TARGET_FILE_DIR:CalcApp>/plugins
)

foreach (plugin ${PLUGIN_TARGETS})
add_custom_command(TARGET CalcApp POST_BUILD
COMMAND ${CMAKE_COMMAND} -E copy
$<TARGET_FILE:${plugin}>
$<TARGET_FILE_DIR:CalcApp>/plugins/
)
endforeach ()

enable_testing()
add_subdirectory(tests)
49 changes: 49 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
# Simple calculator app
## Operators and functions are loaded from DLLs or SOs

### Build:
1. `cmake -B ./build -S ./` (on windows you may want to also have `-g Ninja`)
2. `cmake --build ./build`

This will build all the dlls from ./plugins/ and calculator itself

### Running

Simply start the build/CalcApp.exe executable from build directory

### Usage

Calculator will try to calculate any expression that is passed in first line of stdin and then exit
If any exception happened, that will be seen in console:

### Supported syntax
- Calculator comes with some plugins out of the box (see `plugins/` dir): basic math and some functions
- Calculator supports parentheses, binary and unary operators, functions
- Function's arguments should be passed in parentheses: `sin(3.14)`, not `sin 3.14`
- Function may have any positive integer number of arguments that is `size_t` able to save: `max(1,2,3,4,5,...)`
- Operators should be single characters

### Creating your own dll:
To create your very own function or operator you have to create a separate dll
To make it, follow this steps:

1. create a `*.cpp` file in `plugins/` folder
2. include `#include "../src/plugin_interface.h"` in it
3. Define required functional:
- your function: `anyname`
- FunctionInfo
- `PLUGIN_API const FunctionInfo* get_function_info()`
- If not understood, see example plugins in `plugins/` folder
- Rebuild the app to create shared library binary from your new .cpp

#### Plugin name restrictions:
- Not empty
- Not be '(', ')', ',' - reserved syntax tokens
- Shouldn't start with number
- Operators may have only 1 char as a name
- Plugins within categories (binary operator, unary operator, function) should not share a name inside a category. So, there may not be 2 unary '-' operators, but may be unary '-' and binary '-' and function "-"

#### What's unary, binary and function?
- If plugin is an operator with arity=2 -> binary operator like `1+2`
- If plugin is an operator with arity=1 -> unary prefix operator `-1`, `(-1+2)`, `-(1+2)`
- if plugin is a function it's implemented as prefix function with any arity: `sin(3.14)`, `pow(2, 4)`
17 changes: 17 additions & 0 deletions main.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
#include <iostream>
#include <string>

#include "src/Calculator.hpp"

int main() {
try {
std::string input;
std::getline(std::cin, input);
Calculator calc;
std::cout << calc.evaluate(input) << std::endl;
} catch (const std::exception& e) {
std::cerr << "Error: " << e.what() << std::endl;
return 1;
}
return 0;
}
36 changes: 36 additions & 0 deletions plugins/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
cmake_minimum_required(VERSION 3.14)

# Automatically find all .cpp files in this directory
file(GLOB PLUGIN_SOURCES CONFIGURE_DEPENDS "*.cpp")

if (WIN32)
set(CMAKE_WINDOWS_EXPORT_ALL_SYMBOLS ON)
endif ()

if(NOT PLUGIN_SOURCES)
message(STATUS "No plugin sources found in ${CMAKE_CURRENT_SOURCE_DIR}")
return()
endif()

set(PLUGIN_TARGETS "")

foreach(source_file IN LISTS PLUGIN_SOURCES)
# Get filename without path and extension
get_filename_component(plugin_name ${source_file} NAME_WE)

# Create a shared library (DLL) for each plugin
add_library(${plugin_name} SHARED ${source_file})

set_target_properties(${plugin_name} PROPERTIES
PREFIX "" # No "lib" prefix on Windows
OUTPUT_NAME "${plugin_name}" # Explicit name
ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/static_plugins # optional: control where .a goes
)

list(APPEND PLUGIN_TARGETS ${plugin_name})
endforeach()

# Expose PLUGIN_TARGETS to parent scope so main CMakeLists can copy them
set(PLUGIN_TARGETS ${PLUGIN_TARGETS} PARENT_SCOPE)

message(STATUS "Found ${CMAKE_CURRENT_SOURCE_DIR} plugins: ${PLUGIN_TARGETS}")
9 changes: 9 additions & 0 deletions plugins/PLUGIN_STRUCTURE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@


# Every calculator plugin should have 2 functions in ddlexport:
1. `PLUGIN_API const FunctionInfo* get_function_info();` - возвращает имя функции или оператора.
2. `PLUGIN API double name_eval(const double* args, size_t count)` - вычисляет значение функции от аргументов и возвращает результат, или выбрасывает исключение

### See src/plugin_interface.h for FunctionInfo and PLUGIN_API definition
- `extern "C"` disables name mangling
- `__declspec(dllexport)` exports the symbol from .dll
17 changes: 17 additions & 0 deletions plugins/add_plugin.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
#include "../src/plugin_interface.h"

//количество аргументов функции (оператора)
#define ARGC 2

PLUGIN_API PluginResult add_eval(const double* args, size_t count) {
if (count != ARGC) return PluginResult{0.0, "Should accept 2 arguments"};
return PluginResult{args[0] + args[1], nullptr};
}

static const FunctionInfo info = {
"+", ARGC, 60, Associativity::Left, true, add_eval
};

PLUGIN_API const FunctionInfo* get_function_info() {
return &info;
}
18 changes: 18 additions & 0 deletions plugins/cos_plugin.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
#include "../src/plugin_interface.h"
#include <cmath>

//количество аргументов функции (оператора)
#define ARGC 1

PLUGIN_API PluginResult cos_eval(const double* args, size_t count) {
if (count != ARGC) return PluginResult{0.0, "Should accept 1 argument"};
return PluginResult{std::cos(args[0]), nullptr};
}

static const FunctionInfo info = {
"cos", ARGC, 90, Associativity::Left, false, cos_eval
};

PLUGIN_API const FunctionInfo* get_function_info() {
return &info;
}
20 changes: 20 additions & 0 deletions plugins/div_plugin.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
#include "../src/plugin_interface.h"

#include <stdexcept>

//количество аргументов функции (оператора)
#define ARGC 2

PLUGIN_API PluginResult div_eval(const double* args, size_t count) {
if (count != ARGC) return PluginResult{0.0, "Should accept 2 arguments"};
if (args[1] == 0.0) return PluginResult{0.0, "Division by zero"};
return PluginResult{args[0] / args[1], nullptr};
}

static const FunctionInfo info = {
"/", ARGC, 70, Associativity::Left, true, div_eval
};

PLUGIN_API const FunctionInfo* get_function_info() {
return &info;
}
21 changes: 21 additions & 0 deletions plugins/ln_plugin.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
#include "../src/plugin_interface.h"
#include <cmath>

//количество аргументов функции (оператора)
#define ARGC 1

PLUGIN_API PluginResult ln_eval(const double* args, size_t count) {
if (count != ARGC) return PluginResult{0.0, "Should accept 1 argument"};
if (args[0] <= 0.0) {
return PluginResult{0.0, "Argument must be positive"};
}
return PluginResult{std::log(args[0]), nullptr};
}

static const FunctionInfo info = {
"ln", ARGC, 90, Associativity::Left, false, ln_eval
};

PLUGIN_API const FunctionInfo* get_function_info() {
return &info;
}
16 changes: 16 additions & 0 deletions plugins/mul_plugin.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
#include "../src/plugin_interface.h"

//количество аргументов функции (оператора)
#define ARGC 2
PLUGIN_API PluginResult mul_eval(const double* args, size_t count) {
if (count != ARGC) return PluginResult{0.0, "Should accept 2 arguments"};
return PluginResult{args[0] * args[1], nullptr};
}

static const FunctionInfo info = {
"*", ARGC, 70, Associativity::Left, true, mul_eval
};

PLUGIN_API const FunctionInfo* get_function_info() {
return &info;
}
20 changes: 20 additions & 0 deletions plugins/pow_plugin.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
#include "../src/plugin_interface.h"

#include <cmath>
#include <stdexcept>

//количество аргументов функции (оператора)
#define ARGC 2

PLUGIN_API PluginResult pow_eval(const double* args, size_t) {
double base = args[0], exp = args[1];
return PluginResult{std::pow(base, exp), nullptr};
}

static const FunctionInfo info = {
"^", ARGC, 90, Associativity::Right, true, pow_eval
};

PLUGIN_API const FunctionInfo* get_function_info() {
return &info;
}
16 changes: 16 additions & 0 deletions plugins/sin_plugin.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
#include <cmath>
#include "../src/plugin_interface.h"

//количество аргументов функции (оператора)
#define ARGC 1

PLUGIN_API PluginResult sin_eval(const double* args, size_t count) {
if (count != 1) return PluginResult{0.0, "Should accept 1 argument"};
return PluginResult{std::sin(args[0]), nullptr};
}

static const FunctionInfo info = {
"sin", ARGC, 90, Associativity::Left, false, sin_eval
};

PLUGIN_API const FunctionInfo* get_function_info() { return &info; }
17 changes: 17 additions & 0 deletions plugins/sub_plugin.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
#include "../src/plugin_interface.h"


//количество аргументов функции (оператора)
#define ARGC 2

PLUGIN_API PluginResult sub_eval(const double* args, size_t) {
return PluginResult{args[0] - args[1], nullptr};
}

static const FunctionInfo info = {
"-", ARGC, 60, Associativity::Left, true, sub_eval
};

PLUGIN_API const FunctionInfo* get_function_info() {
return &info;
}
20 changes: 20 additions & 0 deletions plugins/tg_plugin.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
#include "../src/plugin_interface.h"
#include <cmath>
#include <stdexcept>

//количество аргументов функции (оператора)
#define ARGC 1

PLUGIN_API PluginResult tan_eval(const double* args, size_t count) {
if (count != ARGC) return PluginResult{0.0, "Should accept 1 argument"};
double x = args[0];
return PluginResult{std::tan(x), nullptr};
}

static const FunctionInfo info = {
"tg", ARGC, 90, Associativity::Left, false, tan_eval
};

PLUGIN_API const FunctionInfo* get_function_info() {
return &info;
}
20 changes: 20 additions & 0 deletions plugins/unary_minus.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
#include "../src/plugin_interface.h"
#include <cmath>
#include <stdexcept>

#define ARGC 1

PLUGIN_API PluginResult uminus_eval(const double* args, size_t count) {
if (count != ARGC) {
return PluginResult{0.0, "Unary minus: expected 1 argument"};
}
return PluginResult{-args[0], nullptr};
}

static const FunctionInfo info = {
"-", ARGC, 80, Associativity::Right, true, uminus_eval
};

PLUGIN_API const FunctionInfo* get_function_info() {
return &info;
}
18 changes: 18 additions & 0 deletions src/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
cmake_minimum_required(VERSION 3.14)
project(CalcLib LANGUAGES CXX)

set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)

add_library(CalcLib
Tokenizer.cpp
PluginManager.cpp
Token.hpp
Tokenizer.hpp
ShuntingYard.hpp
ShuntingYard.cpp
RpnEvaluator.hpp
RpnEvaluator.cpp
Calculator.hpp
Calculator.cpp
)
11 changes: 11 additions & 0 deletions src/Calculator.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
#include "Calculator.hpp"
#include "Tokenizer.hpp"
#include "ShuntingYard.hpp"
#include "RpnEvaluator.hpp"
#include "PluginManager.hpp"

double Calculator::evaluate(const std::string& expr) {
auto tokens = tokenize(expr);
auto rpn = shuntingYard(tokens, pm);
return evaluateRpn(rpn, pm);
}
11 changes: 11 additions & 0 deletions src/Calculator.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
#pragma once

#include <string>

#include "PluginManager.hpp"

class Calculator {
PluginManager pm;
public:
double evaluate(const std::string& expression);
};
Loading