├── CMakeLists.txt ├── cmake └── CalculateConfig.cmake.in ├── conanfile.py ├── copying ├── example ├── CMakeLists.txt ├── conanfile.txt └── source │ └── calculate.cpp ├── include ├── calculate.hpp └── calculate │ ├── container.hpp │ ├── exception.hpp │ ├── lexer.hpp │ ├── node.hpp │ ├── parser.hpp │ ├── symbol.hpp │ ├── util.hpp │ └── wrapper.hpp ├── readme.md ├── resource ├── calculate.svg └── logo.svg └── test ├── CMakeLists.txt ├── conanfile.txt └── source ├── parser.cpp ├── symbol.cpp └── wrapper.cpp /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.5) 2 | project(Calculate LANGUAGES CXX VERSION 2.1.1) 3 | 4 | 5 | option(CALCULATE_BUILD_EXAMPLES "Add Calculate's example targets" OFF) 6 | option(CALCULATE_BUILD_TESTS "Add Calculate's test targets" OFF) 7 | 8 | if(CMAKE_SOURCE_DIR STREQUAL PROJECT_SOURCE_DIR) 9 | set(CMAKE_CONFIGURATION_TYPES "Release;Debug" CACHE STRING "Configurations" FORCE) 10 | if(NOT CMAKE_BUILD_TYPE) 11 | if(EXISTS "${CMAKE_SOURCE_DIR}/.git") 12 | set(CMAKE_BUILD_TYPE "Debug" CACHE STRING "Build type" FORCE) 13 | else() 14 | set(CMAKE_BUILD_TYPE "Release" CACHE STRING "Build type" FORCE) 15 | endif() 16 | endif() 17 | message(STATUS "Calculate: setting build type to '${CMAKE_BUILD_TYPE}'") 18 | 19 | if(NOT CMAKE_INSTALL_INCLUDEDIR}) 20 | set(CMAKE_INSTALL_INCLUDEDIR "${CMAKE_INSTALL_PREFIX}/include") 21 | endif() 22 | 23 | if(NOT CMAKE_INSTALL_LIBDIR}) 24 | set(CMAKE_INSTALL_LIBDIR "${CMAKE_INSTALL_PREFIX}/lib") 25 | endif() 26 | endif() 27 | 28 | 29 | add_library(Calculate INTERFACE) 30 | 31 | target_compile_features(Calculate INTERFACE cxx_std_14) 32 | 33 | target_include_directories(Calculate 34 | INTERFACE 35 | $ 36 | $ 37 | ) 38 | 39 | add_library(Calculate::Calculate ALIAS Calculate) 40 | 41 | 42 | include(CMakePackageConfigHelpers) 43 | set(CALCULATE_CONFIG_DIR "${CMAKE_INSTALL_LIBDIR}/cmake/Calculate") 44 | 45 | configure_package_config_file( 46 | ${CMAKE_CURRENT_SOURCE_DIR}/cmake/CalculateConfig.cmake.in 47 | ${CMAKE_CURRENT_BINARY_DIR}/CalculateConfig.cmake 48 | INSTALL_DESTINATION ${CALCULATE_CONFIG_DIR} 49 | ) 50 | 51 | write_basic_package_version_file( 52 | "${CMAKE_CURRENT_BINARY_DIR}/CalculateConfigVersion.cmake" 53 | COMPATIBILITY SameMajorVersion 54 | ) 55 | 56 | install(TARGETS Calculate EXPORT CalculateTargets DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}) 57 | install(EXPORT CalculateTargets NAMESPACE Calculate:: DESTINATION ${CALCULATE_CONFIG_DIR}) 58 | install(DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/include/" DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}") 59 | 60 | install( 61 | FILES 62 | "${CMAKE_CURRENT_BINARY_DIR}/CalculateConfig.cmake" 63 | "${CMAKE_CURRENT_BINARY_DIR}/CalculateConfigVersion.cmake" 64 | DESTINATION ${CALCULATE_CONFIG_DIR} 65 | ) 66 | 67 | 68 | if(CALCULATE_BUILD_EXAMPLES) 69 | add_subdirectory(example) 70 | endif() 71 | 72 | if(CALCULATE_BUILD_TESTS) 73 | enable_testing() 74 | add_subdirectory(test) 75 | endif() 76 | -------------------------------------------------------------------------------- /cmake/CalculateConfig.cmake.in: -------------------------------------------------------------------------------- 1 | @PACKAGE_INIT@ 2 | 3 | include("${CMAKE_CURRENT_LIST_DIR}/@targets_export_name@.cmake") 4 | check_required_components("@PROJECT_NAME@") 5 | -------------------------------------------------------------------------------- /conanfile.py: -------------------------------------------------------------------------------- 1 | from conans import ConanFile, CMake 2 | 3 | 4 | class CalculateConan(ConanFile): 5 | name = 'Calculate' 6 | version = '2.1.1' 7 | license = 'MIT' 8 | url = 'https://github.com/newlawrence/Calculate.git' 9 | description = 'Math Expressions Parser Engine' 10 | generators = 'cmake' 11 | exports_sources = [ 12 | 'copying', 13 | 'CMakeLists.txt', 14 | 'cmake/CalculateConfig.cmake.in', 15 | 'include/*', 16 | 'test/*' 17 | ] 18 | 19 | def build_requirements(self): 20 | self.build_requires('Catch2/2.9.1@catchorg/stable') 21 | 22 | def build(self): 23 | cmake = CMake(self) 24 | cmake.definitions['CALCULATE_BUILD_TESTS'] = 'ON' 25 | cmake.configure() 26 | cmake.build(['--target', 'make_test']) 27 | cmake.test() 28 | 29 | def package(self): 30 | self.copy('*.hpp') 31 | self.copy('copying', dst='.') 32 | 33 | def package_id(self): 34 | self.info.header_only() 35 | -------------------------------------------------------------------------------- /copying: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016-2018 by Alberto Lorenzo Márquez 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /example/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.5) 2 | project(Calculate-Examples LANGUAGES CXX) 3 | 4 | 5 | if(CMAKE_SOURCE_DIR STREQUAL PROJECT_SOURCE_DIR) 6 | set(CMAKE_CONFIGURATION_TYPES "Release;Debug" CACHE STRING "Configurations" FORCE) 7 | if(NOT CMAKE_BUILD_TYPE) 8 | set(CMAKE_BUILD_TYPE "Debug" CACHE STRING "Build type" FORCE) 9 | endif() 10 | else() 11 | message(STATUS "Calculate: examples enabled") 12 | endif() 13 | 14 | find_package(Boost QUIET) 15 | if(EXISTS "${CMAKE_BINARY_DIR}/example/conanbuildinfo.cmake") 16 | include("${CMAKE_BINARY_DIR}/example/conanbuildinfo.cmake") 17 | conan_basic_setup(TARGETS NO_OUTPUT_DIRS) 18 | set(CALCULATE_EXAMPLES_USE_CONAN TRUE) 19 | elseif(EXISTS "${CMAKE_BINARY_DIR}/conanbuildinfo.cmake") 20 | include("${CMAKE_BINARY_DIR}/conanbuildinfo.cmake") 21 | conan_basic_setup(TARGETS NO_OUTPUT_DIRS) 22 | set(CALCULATE_EXAMPLES_USE_CONAN TRUE) 23 | elseif(Boost_FOUND) 24 | find_package(Boost REQUIRED COMPONENTS system filesystem program_options) 25 | include_directories(${Boost_INCLUDE_DIRS}) 26 | string(REPLACE ";" "\n " boost_libraries "${Boost_LIBRARIES}") 27 | set(CALCULATE_EXAMPLES_USE_CONAN FALSE) 28 | else() 29 | message(FATAL_ERROR "Calculate: missing Boost libraries") 30 | endif() 31 | 32 | 33 | add_executable(example "${CMAKE_CURRENT_SOURCE_DIR}/source/calculate.cpp") 34 | 35 | set_target_properties(example 36 | PROPERTIES 37 | CXX_STANDARD_REQUIRED YES 38 | CXX_EXTENSIONS NO 39 | RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/bin" 40 | OUTPUT_NAME calculate 41 | ) 42 | 43 | if(CMAKE_SOURCE_DIR STREQUAL PROJECT_SOURCE_DIR) 44 | target_include_directories(example PRIVATE "${CMAKE_SOURCE_DIR}/../include") 45 | else() 46 | target_include_directories(example PRIVATE "${CMAKE_SOURCE_DIR}/include") 47 | endif() 48 | 49 | target_compile_features(example PRIVATE cxx_std_14) 50 | 51 | target_compile_options(example 52 | PRIVATE 53 | $<$: -pedantic -Wall -Wextra -Werror -Wno-noexcept-type> 54 | $<$: -pedantic -Wall -Wextra -Werror -Qunused-arguments> 55 | ) 56 | 57 | if(CALCULATE_EXAMPLES_USE_CONAN) 58 | target_link_libraries(example CONAN_PKG::boost) 59 | else() 60 | target_link_libraries(example ${Boost_LIBRARIES}) 61 | endif() 62 | -------------------------------------------------------------------------------- /example/conanfile.txt: -------------------------------------------------------------------------------- 1 | [requires] 2 | boost/1.70.0@conan/stable 3 | 4 | [generators] 5 | cmake 6 | -------------------------------------------------------------------------------- /example/source/calculate.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | #include 9 | #include 10 | 11 | #include "calculate.hpp" 12 | 13 | 14 | namespace fs = boost::filesystem; 15 | namespace po = boost::program_options; 16 | 17 | template 18 | void run(std::string, std::vector, const po::variables_map&); 19 | 20 | 21 | int main(int argc, char *argv[]) { 22 | fs::path program{argv[0]}; 23 | po::variables_map arguments; 24 | std::string string; 25 | std::vector variables; 26 | 27 | po::options_description named_args("Options"); 28 | named_args.add_options() 29 | ("expression,e", po::value(&string)->required(), "Expression to parse") 30 | ("help,h", "Show this help message") 31 | ("var,v", po::value>(&variables), "Add variable (var:value)") 32 | ("postfix,p", "Use postfix parser") 33 | ("complex,c", "Use complex parser") 34 | ("optimize,o", "Optimize expression") 35 | ("iter,i", po::value()->default_value(1000), "Performance iterations") 36 | ("analysis,a", "Print analysis") 37 | ; 38 | po::positional_options_description positional_args; 39 | positional_args.add("expression", 1); 40 | 41 | try { 42 | po::store( 43 | po::command_line_parser(argc, argv) 44 | .options(named_args) 45 | .positional(positional_args) 46 | .run(), 47 | arguments 48 | ); 49 | 50 | if (arguments.count("help")) { 51 | std::cout << 52 | "Usage: " << program.filename() << 53 | " [options] {-e [ --expression ]} " 54 | << std::endl; 55 | std::cout << named_args << std::endl; 56 | return 0; 57 | } 58 | po::notify(arguments); 59 | 60 | if (arguments.count("complex")) 61 | run(string, variables, arguments); 62 | else 63 | run(string, variables, arguments); 64 | } 65 | catch (const po::error& error) { 66 | std::cerr << "Command line error: " << error.what() << std::endl; 67 | return 1; 68 | } 69 | catch (const calculate::BaseError& error) { 70 | std::cerr << error.what() << std::endl; 71 | return 2; 72 | } 73 | return 0; 74 | } 75 | 76 | 77 | template 78 | void run( 79 | std::string expression, 80 | std::vector variables, 81 | const po::variables_map& arguments 82 | ) { 83 | using Type = typename Parser::Type; 84 | 85 | std::chrono::steady_clock::time_point begin; 86 | std::chrono::steady_clock::time_point::rep build_time, eval_time; 87 | std::size_t iterations = arguments["iter"].as(); 88 | Type result; 89 | 90 | auto parser = Parser{}; 91 | auto& lexer = parser.lexer(); 92 | 93 | if (arguments.count("optimize")) 94 | parser.optimize = true; 95 | 96 | std::vector values; 97 | if (arguments.count("var")) { 98 | for (auto& var : variables) { 99 | auto sep = var.find(":"); 100 | if (sep == 0 || sep > var.size() - 2) 101 | throw po::error("bad variable input '" + var + "'"); 102 | values.push_back(lexer.to_value(var.substr(sep + 1, var.size()))); 103 | var = var.substr(0, sep); 104 | } 105 | } 106 | 107 | auto parse = arguments.count("postfix") ? 108 | &Parser::template from_postfix&> : 109 | &Parser::template from_infix&>; 110 | 111 | auto function = (parser.*parse)(expression, variables); 112 | if (arguments.count("analysis")) { 113 | begin = std::chrono::steady_clock::now(); 114 | for (std::size_t i = 0; i < iterations; i++) 115 | (parser.*parse)(expression, variables); 116 | build_time = std::chrono::duration_cast( 117 | std::chrono::steady_clock::now() - begin 118 | ).count() / iterations; 119 | } 120 | 121 | result = function(values); 122 | if (arguments.count("analysis")) { 123 | begin = std::chrono::steady_clock::now(); 124 | for (std::size_t i = 0; i < iterations; i++) 125 | function(values); 126 | eval_time = std::chrono::duration_cast( 127 | std::chrono::steady_clock::now() - begin 128 | ).count() / iterations; 129 | } 130 | 131 | if (arguments.count("analysis")) { 132 | std::cout << "Infix notation: " << function.infix() << "\n"; 133 | std::cout << "Postfix notation: " << function.postfix() << "\n"; 134 | if (arguments.count("var")) { 135 | std::cout << "Variables: "; 136 | for (const auto& var : variables) 137 | std::cout << var << " "; 138 | std::cout << "\n"; 139 | std::cout << "Values: "; 140 | for (const auto& val : values) 141 | std::cout << lexer.to_string(val) << " "; 142 | std::cout << "\n"; 143 | } 144 | std::cout << "Result: " << lexer.to_string(result) << "\n"; 145 | std::cout << "Iterations: " << iterations << "\n"; 146 | std::cout << "Building time: " << build_time << " us" << "\n"; 147 | std::cout << "Evaluation time: " << eval_time << " ns" << std::endl; 148 | } 149 | else 150 | std::cout << lexer.to_string(result) << std::endl; 151 | } 152 | -------------------------------------------------------------------------------- /include/calculate.hpp: -------------------------------------------------------------------------------- 1 | /* 2 | Calculate - Version 2.1.1rc10 3 | Last modified 2018/07/28 4 | Released under MIT license 5 | Copyright (c) 2016-2018 Alberto Lorenzo 6 | */ 7 | 8 | 9 | #ifndef __CALCULATE_HPP__ 10 | #define __CALCULATE_HPP__ 11 | 12 | #include "calculate/parser.hpp" 13 | 14 | 15 | namespace calculate { 16 | 17 | namespace defaults { 18 | 19 | template T add(const T& x, const T& y) noexcept { return x + y; } 20 | template T sub(const T& x, const T& y) noexcept { return x - y; } 21 | template T mul(const T& x, const T& y) noexcept { return x * y; } 22 | template T div(const T& x, const T& y) noexcept { return x / y; } 23 | 24 | struct Precedence { 25 | static constexpr std::size_t very_low = 1111u; 26 | static constexpr std::size_t low = 2222u; 27 | static constexpr std::size_t normal = 5555u; 28 | static constexpr std::size_t high = 8888u; 29 | static constexpr std::size_t very_high = 9999u; 30 | }; 31 | 32 | } 33 | 34 | 35 | class Parser : public BaseParser { 36 | public: 37 | Parser(const Lexer& lexer=make_lexer()) : 38 | BaseParser{lexer} 39 | { 40 | using namespace defaults; 41 | using F1 = Type(*)(Type); 42 | using F2 = Type(*)(Type, Type); 43 | using F3 = Type(*)(Type, Type, Type); 44 | 45 | auto pow = [](Type x1, Type x2) noexcept { 46 | if (x2 <= 0. || x2 > 256 || std::trunc(x2) != x2) 47 | return std::pow(x1, x2); 48 | 49 | auto exp = static_cast(x2); 50 | auto prod = 1.; 51 | while (exp) { 52 | if (exp & 1) 53 | prod *= x1; 54 | exp >>= 1; 55 | x1 *= x1; 56 | } 57 | return prod; 58 | }; 59 | 60 | auto fact = [](Type x) noexcept { 61 | if (x > 256) 62 | return std::numeric_limits::infinity(); 63 | 64 | auto prod = 1.; 65 | for (auto i = 2.; i <= x; i++) 66 | prod *= i; 67 | return prod; 68 | }; 69 | 70 | constants.insert({ 71 | {"pi", 3.14159265358979323846}, 72 | {"e", 2.71828182845904523536}, 73 | {"phi", 1.61803398874989484820}, 74 | {"gamma", 0.57721566490153286060} 75 | }); 76 | 77 | functions.insert({ 78 | {"id", [](Type x) noexcept { return x; }}, 79 | {"neg", [](Type x) noexcept { return -x; }}, 80 | {"inv", [](Type x) noexcept { return Type{1} / x; }}, 81 | {"fabs", static_cast(std::fabs)}, 82 | {"abs", static_cast(std::abs)}, 83 | {"fma", static_cast(std::fma)}, 84 | {"copysign", static_cast(std::copysign)}, 85 | {"nextafter", static_cast(std::nextafter)}, 86 | {"fdim", static_cast(std::fdim)}, 87 | {"fmax", static_cast(std::fmax)}, 88 | {"fmin", static_cast(std::fmin)}, 89 | {"fdim", static_cast(std::fdim)}, 90 | {"fmax", static_cast(std::fmax)}, 91 | {"fmin", static_cast(std::fmin)}, 92 | {"ceil", static_cast(std::ceil)}, 93 | {"floor", static_cast(std::floor)}, 94 | {"fmod", static_cast(std::fmod)}, 95 | {"trunc", static_cast(std::trunc)}, 96 | {"round", static_cast(std::round)}, 97 | {"rint", static_cast(std::rint)}, 98 | {"nearbyint", static_cast(std::nearbyint)}, 99 | {"remainder", static_cast(std::remainder)}, 100 | {"pow", static_cast(std::pow)}, 101 | {"sqrt", static_cast(std::sqrt)}, 102 | {"cbrt", static_cast(std::cbrt)}, 103 | {"hypot", static_cast(std::hypot)}, 104 | {"exp", static_cast(std::exp)}, 105 | {"expm1", static_cast(std::expm1)}, 106 | {"exp2", static_cast(std::exp2)}, 107 | {"log", static_cast(std::log)}, 108 | {"log10", static_cast(std::log10)}, 109 | {"log1p", static_cast(std::log1p)}, 110 | {"log2", static_cast(std::log2)}, 111 | {"logb", static_cast(std::logb)}, 112 | {"sin", static_cast(std::sin)}, 113 | {"cos", static_cast(std::cos)}, 114 | {"tan", static_cast(std::tan)}, 115 | {"asin", static_cast(std::asin)}, 116 | {"acos", static_cast(std::acos)}, 117 | {"atan", static_cast(std::atan)}, 118 | {"atan2", static_cast(std::atan2)}, 119 | {"sinh", static_cast(std::sinh)}, 120 | {"cosh", static_cast(std::cosh)}, 121 | {"tanh", static_cast(std::tanh)}, 122 | {"asinh", static_cast(std::asinh)}, 123 | {"acosh", static_cast(std::acosh)}, 124 | {"atanh", static_cast(std::atanh)}, 125 | {"erf", static_cast(std::erf)}, 126 | {"erfc", static_cast(std::erfc)}, 127 | {"tgamma", static_cast(std::tgamma)}, 128 | {"lgamma", static_cast(std::lgamma)}, 129 | {"fact", fact} 130 | }); 131 | 132 | operators.insert({ 133 | {"+", {add, Precedence::low, Associativity::FULL}}, 134 | {"-", {sub, Precedence::low, Associativity::LEFT}}, 135 | {"*", {mul, Precedence::normal, Associativity::FULL}}, 136 | {"/", {div, Precedence::normal, Associativity::LEFT}}, 137 | {"%", {static_cast(std::fmod), Precedence::normal, Associativity::LEFT}}, 138 | {"^", {pow, Precedence::high, Associativity::RIGHT}} 139 | }); 140 | 141 | prefixes.insert({ 142 | {"+", "id"}, 143 | {"-", "neg"} 144 | }); 145 | 146 | suffixes.insert({ 147 | {"!", "fact"} 148 | }); 149 | } 150 | }; 151 | 152 | 153 | class ComplexParser : public BaseParser> { 154 | public: 155 | ComplexParser(const Lexer& lexer=make_lexer()) : 156 | BaseParser{lexer} 157 | { 158 | using namespace std::complex_literals; 159 | using namespace defaults; 160 | using F1 = Type(*)(const Type&); 161 | using F2 = Type(*)(const Type&, const Type&); 162 | 163 | auto polar = [](const Type& z1, const Type& z2) noexcept { 164 | return z1 * std::exp(1.i * z2); 165 | }; 166 | 167 | constants.insert({ 168 | {"i", Type{0., 1.}}, 169 | {"pi", Type{3.14159265358979323846, 0.}}, 170 | {"e", Type{2.71828182845904523536, 0.}}, 171 | {"phi", Type{1.61803398874989484820, 0.}}, 172 | {"gamma", Type{0.57721566490153286060, 0.}} 173 | }); 174 | 175 | functions.insert({ 176 | {"id", [](const Type& z) noexcept { return z; }}, 177 | {"neg", [](const Type& z) noexcept { return -z; }}, 178 | {"inv", [](const Type& z) noexcept { return Type{1} / z; }}, 179 | {"real", [](const Type& z) noexcept { return Type{std::real(z)}; }}, 180 | {"imag", [](const Type& z) noexcept { return Type{std::imag(z)}; }}, 181 | {"abs", [](const Type& z) noexcept { return Type{std::abs(z)}; }}, 182 | {"arg", [](const Type& z) noexcept { return Type{std::arg(z)}; }}, 183 | {"norm", [](const Type& z) noexcept { return Type{std::norm(z)}; }}, 184 | {"polar", polar}, 185 | {"conj", static_cast(std::conj)}, 186 | {"proj", static_cast(std::exp)}, 187 | {"exp", static_cast(std::exp)}, 188 | {"log", static_cast(std::log)}, 189 | {"log10", static_cast(std::log10)}, 190 | {"pow", static_cast(std::pow)}, 191 | {"sqrt", static_cast(std::sqrt)}, 192 | {"sin", static_cast(std::sin)}, 193 | {"sinh", static_cast(std::sinh)}, 194 | {"cos", static_cast(std::cos)}, 195 | {"cosh", static_cast(std::cosh)}, 196 | {"tan", static_cast(std::tan)}, 197 | {"tanh", static_cast(std::tanh)}, 198 | {"asin", static_cast(std::asin)}, 199 | {"asinh", static_cast(std::asinh)}, 200 | {"acos", static_cast(std::acos)}, 201 | {"acosh", static_cast(std::acosh)}, 202 | {"atan", static_cast(std::atan)}, 203 | {"atanh", static_cast(std::atanh)} 204 | }); 205 | 206 | operators.insert({ 207 | {"+", {add, Precedence::low, Associativity::FULL}}, 208 | {"-", {sub, Precedence::low, Associativity::LEFT}}, 209 | {"*", {mul, Precedence::normal, Associativity::FULL}}, 210 | {"/", {div, Precedence::normal, Associativity::LEFT}}, 211 | {"^", {static_cast(std::pow), Precedence::high, Associativity::RIGHT}} 212 | }); 213 | 214 | prefixes.insert({ 215 | {"+", "id"}, 216 | {"-", "neg"} 217 | }); 218 | } 219 | }; 220 | 221 | } 222 | 223 | #endif 224 | -------------------------------------------------------------------------------- /include/calculate/container.hpp: -------------------------------------------------------------------------------- 1 | /* 2 | Calculate - Version 2.1.1rc10 3 | Last modified 2018/08/28 4 | Released under MIT license 5 | Copyright (c) 2016-2018 Alberto Lorenzo 6 | */ 7 | 8 | 9 | #ifndef __CALCULATE_CONTAINER_HPP__ 10 | #define __CALCULATE_CONTAINER_HPP__ 11 | 12 | #include 13 | #include 14 | 15 | #include "exception.hpp" 16 | 17 | 18 | namespace calculate { 19 | 20 | template 21 | class SymbolContainer final : std::unordered_map { 22 | friend Parser; 23 | using Base = std::unordered_map; 24 | 25 | using Constant = typename Parser::Constant; 26 | using Function = typename Parser::Function; 27 | using Operator = typename Parser::Operator; 28 | 29 | public: 30 | using Lexer = typename Parser::Lexer; 31 | using Type = typename Parser::Type; 32 | 33 | using typename Base::key_type; 34 | using typename Base::mapped_type; 35 | using typename Base::value_type; 36 | using typename Base::iterator; 37 | using typename Base::const_iterator; 38 | 39 | private: 40 | Lexer* _lexer; 41 | 42 | explicit SymbolContainer(Lexer* lexer) : _lexer{lexer} {} 43 | 44 | SymbolContainer(const SymbolContainer&) = default; 45 | SymbolContainer(SymbolContainer&&) = default; 46 | ~SymbolContainer() = default; 47 | 48 | SymbolContainer& operator=(const SymbolContainer&) = default; 49 | SymbolContainer& operator=(SymbolContainer&&) = default; 50 | 51 | void _validate(const std::string& key, Constant*) const { 52 | if (!std::regex_match(key, _lexer->name_regex)) 53 | throw UnsuitableName{key}; 54 | } 55 | 56 | void _validate(const std::string& key, Function*) const { 57 | if (!std::regex_match(key, _lexer->name_regex)) 58 | throw UnsuitableName{key}; 59 | } 60 | 61 | void _validate(const std::string& key, Operator*) const { 62 | if (!std::regex_match(key, _lexer->sign_regex)) 63 | throw UnsuitableName{key}; 64 | } 65 | 66 | void _validate(const std::string& key) const { 67 | _validate(key, static_cast(nullptr)); 68 | } 69 | 70 | public: 71 | const Lexer& lexer() const noexcept { return *_lexer; } 72 | 73 | using Base::begin; 74 | using Base::end; 75 | using Base::cbegin; 76 | using Base::cend; 77 | 78 | using Base::empty; 79 | using Base::size; 80 | using Base::find; 81 | using Base::count; 82 | using Base::at; 83 | 84 | using Base::erase; 85 | using Base::clear; 86 | using Base::swap; 87 | using Base::reserve; 88 | 89 | mapped_type& operator[](const key_type& key) { return Base::find(key)->second; } 90 | 91 | mapped_type& operator[](key_type&& key) { return Base::find(std::move(key))->second; } 92 | 93 | template 94 | std::pair emplace(const std::string& key, Args&&... args) { 95 | _validate(key); 96 | return Base::emplace(key, std::forward(args)...); 97 | } 98 | 99 | std::pair insert(const value_type& value) { 100 | _validate(value.first); 101 | return Base::insert(value); 102 | } 103 | 104 | template 105 | std::pair insert(Value&& value) { 106 | _validate(value.first); 107 | return Base::insert(std::forward(value)); 108 | } 109 | 110 | template 111 | void insert(Iterator first, Iterator last) { 112 | for (auto element = first; element != last; ++element) 113 | _validate(element->first); 114 | Base::insert(first, last); 115 | } 116 | 117 | void insert(std::initializer_list list) { 118 | for (const auto& element : list) 119 | _validate(element.first); 120 | Base::insert(list); 121 | } 122 | }; 123 | 124 | 125 | template 126 | class AliasContainer final : std::unordered_map { 127 | friend Parser; 128 | using Base = std::unordered_map; 129 | 130 | public: 131 | using Lexer = typename Parser::Lexer; 132 | using Type = typename Parser::Type; 133 | 134 | using typename Base::key_type; 135 | using typename Base::mapped_type; 136 | using typename Base::value_type; 137 | using typename Base::iterator; 138 | using typename Base::const_iterator; 139 | 140 | private: 141 | Lexer* _lexer; 142 | 143 | explicit AliasContainer(Lexer* lexer) : _lexer{lexer} {} 144 | 145 | AliasContainer(const AliasContainer&) = default; 146 | AliasContainer(AliasContainer&&) = default; 147 | ~AliasContainer() = default; 148 | 149 | AliasContainer& operator=(const AliasContainer&) = default; 150 | AliasContainer& operator=(AliasContainer&&) = default; 151 | 152 | void _validate(const std::string& key, const std::string& value) const { 153 | if (!std::regex_match(key, _lexer->sign_regex)) 154 | throw UnsuitableName{key}; 155 | if (!std::regex_match(value, _lexer->name_regex)) 156 | throw UnsuitableName{value}; 157 | } 158 | 159 | public: 160 | const Lexer& lexer() const noexcept { return *_lexer; } 161 | 162 | using Base::begin; 163 | using Base::end; 164 | using Base::cbegin; 165 | using Base::cend; 166 | 167 | using Base::empty; 168 | using Base::size; 169 | using Base::find; 170 | using Base::count; 171 | using Base::at; 172 | 173 | using Base::erase; 174 | using Base::clear; 175 | using Base::swap; 176 | using Base::reserve; 177 | 178 | mapped_type& operator[](const key_type& key) { return Base::find(key)->second; } 179 | 180 | mapped_type& operator[](key_type&& key) { return Base::find(std::move(key))->second; } 181 | 182 | template 183 | std::pair emplace(const std::string& key, Value&& value) { 184 | _validate(key, value); 185 | return Base::emplace(key, std::forward(value)); 186 | } 187 | 188 | std::pair insert(const value_type& value) { 189 | _validate(value.first, value.second); 190 | return Base::insert(value); 191 | } 192 | 193 | template 194 | std::pair insert(Value&& value) { 195 | _validate(value.first, value.second); 196 | return Base::insert(std::forward(value)); 197 | } 198 | 199 | template 200 | void insert(Iterator first, Iterator last) { 201 | for (auto element = first; element != last; ++element) 202 | _validate(element->first, element->second); 203 | Base::insert(first, last); 204 | } 205 | 206 | void insert(std::initializer_list list) { 207 | for (const auto& element : list) 208 | _validate(element.first, element.second); 209 | Base::insert(list); 210 | } 211 | }; 212 | 213 | } 214 | 215 | #endif 216 | -------------------------------------------------------------------------------- /include/calculate/exception.hpp: -------------------------------------------------------------------------------- 1 | /* 2 | Calculate - Version 2.1.1rc10 3 | Last modified 2018/02/27 4 | Released under MIT license 5 | Copyright (c) 2016-2018 Alberto Lorenzo 6 | */ 7 | 8 | 9 | #ifndef __CALCULATE_EXCEPTION_HPP__ 10 | #define __CALCULATE_EXCEPTION_HPP__ 11 | 12 | #include 13 | #include 14 | 15 | 16 | namespace calculate { 17 | 18 | struct BaseError : public std::runtime_error { 19 | explicit BaseError(std::string what) : 20 | runtime_error{std::move(what)} 21 | {} 22 | 23 | BaseError() : 24 | runtime_error{"Base error: unexpected error"} 25 | {} 26 | }; 27 | 28 | 29 | struct LexerError : BaseError { 30 | explicit LexerError(const std::string& what) : 31 | BaseError{"Lexer error: " + what} 32 | {} 33 | }; 34 | 35 | struct BadCast : BaseError { 36 | explicit BadCast(const std::string& token) : 37 | BaseError{"Bad cast: cannot perform numeric conversion: '" + token + "'"} 38 | {} 39 | }; 40 | 41 | struct ArgumentsMismatch : BaseError { 42 | const std::size_t needed; 43 | const std::size_t provided; 44 | 45 | ArgumentsMismatch(std::size_t n, std::size_t p) : 46 | BaseError{ 47 | "Arguments mismatch: " + std::to_string(n) + 48 | " needed argument" + (n == 1 ? "" : "s") + 49 | " vs " + std::to_string(p) + " provided" 50 | }, 51 | needed{n}, 52 | provided{p} 53 | {} 54 | 55 | ArgumentsMismatch(const std::string& token, std::size_t n, std::size_t p) : 56 | BaseError{ 57 | "Arguments mismatch: '" + token + "' " + std::to_string(n) + 58 | " needed argument" + (n == 1 ? "" : "s") + 59 | " vs " + std::to_string(p) + " provided" 60 | }, 61 | needed{n}, 62 | provided{p} 63 | {} 64 | }; 65 | 66 | struct EmptyExpression : BaseError { 67 | EmptyExpression() : 68 | BaseError{"Empty expression"} 69 | {} 70 | }; 71 | 72 | struct ParenthesisMismatch : BaseError { 73 | ParenthesisMismatch() : 74 | BaseError{"Parenthesis mismatch"} 75 | {} 76 | }; 77 | 78 | struct RepeatedSymbol : BaseError { 79 | const std::string token; 80 | 81 | explicit RepeatedSymbol(const std::string& t) : 82 | BaseError{"Repeated symbol: '" + t + "'"}, 83 | token{t} 84 | {} 85 | }; 86 | 87 | struct SyntaxError : BaseError { 88 | explicit SyntaxError(const std::string& what) : 89 | BaseError{"Syntax error: " + what} 90 | {} 91 | 92 | SyntaxError() : 93 | BaseError{"Syntax error"} 94 | {} 95 | }; 96 | 97 | struct UndefinedSymbol : BaseError { 98 | const std::string token; 99 | 100 | explicit UndefinedSymbol(const std::string& t) : 101 | BaseError{"Undefined symbol: '" + t + "'"}, 102 | token{t} 103 | {} 104 | }; 105 | 106 | struct UnsuitableName : BaseError { 107 | const std::string token; 108 | 109 | explicit UnsuitableName(const std::string& t) : 110 | BaseError{"Unsuitable symbol name: '" + t + "'"}, 111 | token{t} 112 | {} 113 | }; 114 | 115 | struct UnusedSymbol : BaseError { 116 | const std::string token; 117 | 118 | explicit UnusedSymbol(const std::string& t) : 119 | BaseError{"Unused symbol: '" + t + "'"}, 120 | token{t} 121 | {} 122 | }; 123 | 124 | } 125 | 126 | #endif 127 | -------------------------------------------------------------------------------- /include/calculate/lexer.hpp: -------------------------------------------------------------------------------- 1 | /* 2 | Calculate - Version 2.1.1rc10 3 | Last modified 2018/07/28 4 | Released under MIT license 5 | Copyright (c) 2016-2018 Alberto Lorenzo 6 | */ 7 | 8 | 9 | #ifndef __CALCULATE_LEXER_HPP__ 10 | #define __CALCULATE_LEXER_HPP__ 11 | 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include 20 | 21 | #include "util.hpp" 22 | 23 | 24 | namespace calculate { 25 | 26 | namespace defaults { 27 | 28 | constexpr const char left[] = "("; 29 | constexpr const char right[] = ")"; 30 | constexpr const char separator[] = ","; 31 | 32 | 33 | template 34 | constexpr const char* real = 35 | R"(^[+\-]?\d+$)"; 36 | 37 | template<> 38 | constexpr const char* real = 39 | R"(^[+\-]?(?:(?:NaN|Inf)|(?:(?:\d+\.?\d*|\.\d+)(?:[eE][+\-]?\d+)?))$)"; 40 | 41 | template 42 | constexpr const char* complex = 43 | R"(^(?:(?:(?:[+\-]?\d+?)(?:[+\-]?\d+?)[ij])|(?:(?:[+\-]?\d+)[ij]?))$)"; 44 | 45 | template<> 46 | constexpr const char* complex = 47 | R"(^(?:)" 48 | R"((?:(?:[+\-]?(?:(?:NaN|Inf)|(?:(?:\d+\.?\d*?|\.\d+?)(?:[eE][+\-]?\d+?)?))))" 49 | R"((?:[+\-](?:(?:NaN|Inf)|(?:(?:\d+\.?\d*?|\.\d+?)(?:[eE][+\-]?\d+?)?)))[ij])|)" 50 | R"((?:(?:[+\-]?(?:(?:NaN|Inf)|(?:(?:\d+\.?\d*|\.\d+)(?:[eE][+\-]?\d+)?)))[ij]?))" 51 | R"()$)"; 52 | 53 | template 54 | constexpr const char* number = real>; 55 | 56 | template 57 | constexpr const char* number> = complex>; 58 | 59 | constexpr const char name[] = R"(^[A-Za-z_]+[A-Za-z_\d]*$)"; 60 | constexpr const char sign[] = R"(^(?:[^A-Za-z\d.(),_\s]|(?:\.(?!\d)))+$)"; 61 | 62 | } 63 | 64 | 65 | namespace detail { 66 | 67 | constexpr const char scape[] = {'\\', '.', '^', '$', '*', '+', '?', '(', ')', '[', ']', '{', '}'}; 68 | 69 | constexpr const char split[] = R"(^(?:(?:(.*[^ij])([+\-].+)[ij])|(.*[^ij])|(.+)[ij])$)"; 70 | 71 | 72 | template 73 | Type read(std::istringstream& istream, const std::string& token) { 74 | if (token == "NaN" || token == "+NaN" || token == "-NaN") 75 | return std::numeric_limits::quiet_NaN(); 76 | if (token == "Inf" || token == "+Inf") 77 | return std::numeric_limits::infinity(); 78 | if (token == "-Inf") 79 | return -std::numeric_limits::infinity(); 80 | 81 | Type value; 82 | istream.str(token); 83 | istream.clear(); 84 | istream >> value; 85 | return value; 86 | } 87 | 88 | template 89 | std::string write(std::ostringstream& ostream, const Type& value) { 90 | if (std::isnan(value)) 91 | return "NaN"; 92 | if (std::isinf(value)) 93 | return std::signbit(value) ? "-Inf" : "+Inf"; 94 | 95 | ostream.clear(); 96 | ostream.str(""); 97 | ostream << value; 98 | return ostream.str(); 99 | } 100 | 101 | } 102 | 103 | 104 | template 105 | class BaseLexer { 106 | public: 107 | enum class TokenType { NUMBER, NAME, SIGN, LEFT, RIGHT, SEPARATOR }; 108 | 109 | struct Token { 110 | std::string token; 111 | TokenType type; 112 | }; 113 | 114 | struct PrefixedValue { 115 | std::string prefix; 116 | std::string value; 117 | }; 118 | 119 | const std::string number; 120 | const std::string name; 121 | const std::string sign; 122 | 123 | const std::regex number_regex; 124 | const std::regex name_regex; 125 | const std::regex sign_regex; 126 | 127 | const std::string left; 128 | const std::string right; 129 | const std::string separator; 130 | 131 | private: 132 | const std::regex _splitter_regex; 133 | const std::regex _tokenizer_regex; 134 | 135 | bool _match(const std::smatch& match, TokenType type) const noexcept { 136 | return !match[static_cast(type) + 1].str().empty(); 137 | } 138 | 139 | std::string _adapt_regex(std::string&& regex) const { 140 | if (regex.front() != '^') 141 | regex.insert(0, 1, '^'); 142 | if (regex.back() != '$') 143 | regex.append(1, '$'); 144 | 145 | try{ 146 | std::regex{regex}; 147 | } 148 | catch(const std::regex_error&) { 149 | throw LexerError{"bad regex '" + regex + "'"}; 150 | } 151 | return std::move(regex); 152 | } 153 | 154 | std::string _generate_tokenizer() const { 155 | std::string tokenizer{}; 156 | 157 | auto escape = [](std::string token) { 158 | for (const auto& character : detail::scape) { 159 | size_t index = 0; 160 | while (true) { 161 | index = token.find(character, index); 162 | if (index == std::string::npos) 163 | break; 164 | token.insert(index, 1, '\\'); 165 | index += 2; 166 | } 167 | } 168 | return token; 169 | }; 170 | 171 | tokenizer += "(" + number.substr(1, number.size() - 2) + ")|"; 172 | tokenizer += "(" + name.substr(1, name.size() - 2) + ")|"; 173 | tokenizer += "(" + sign.substr(1, sign.size() - 2) + ")|"; 174 | tokenizer += "(" + escape(left) + ")|"; 175 | tokenizer += "(" + escape(right) + ")|"; 176 | tokenizer += "(" + escape(separator) + ")"; 177 | return tokenizer; 178 | } 179 | 180 | template 181 | std::vector _tokenize(std::string string) const { 182 | std::vector tokens{}; 183 | std::smatch match{}; 184 | 185 | auto last = TokenType::LEFT; 186 | while (std::regex_search(string, match, _tokenizer_regex)) { 187 | auto token = match.str(); 188 | 189 | if (_match(match, TokenType::NUMBER)) { 190 | std::sregex_token_iterator 191 | nums{token.begin(), token.end(), _splitter_regex, -1}, 192 | sgns{token.begin(), token.end(), _splitter_regex}, 193 | end{}; 194 | 195 | if (nums->str().empty()) { 196 | auto sgn = (sgns++)->str(), num = ((++nums)++)->str(); 197 | if ( 198 | infix && 199 | last != TokenType::SIGN && 200 | last != TokenType::LEFT && 201 | last != TokenType::SEPARATOR 202 | ) { 203 | tokens.push_back({std::move(sgn), TokenType::SIGN}); 204 | tokens.push_back({std::move(num), TokenType::NUMBER}); 205 | } 206 | else 207 | tokens.push_back({sgn + num, TokenType::NUMBER}); 208 | } 209 | else 210 | tokens.push_back({(nums++)->str(), TokenType::NUMBER}); 211 | 212 | while (nums != end && sgns != end) { 213 | auto sgn = (sgns++)->str(), num = (nums++)->str(); 214 | if (std::regex_match(tokens.back().token, number_regex)) { 215 | tokens.push_back({sgn, TokenType::SIGN}); 216 | tokens.push_back({num, TokenType::NUMBER}); 217 | } 218 | else 219 | tokens.back().token += sgn + num; 220 | } 221 | } 222 | else if (_match(match, TokenType::NAME)) 223 | tokens.push_back({std::move(token), TokenType::NAME}); 224 | else if (_match(match, TokenType::SIGN)) 225 | tokens.push_back({std::move(token), TokenType::SIGN}); 226 | else if (_match(match, TokenType::LEFT)) 227 | tokens.push_back({std::move(token), TokenType::LEFT}); 228 | else if (_match(match, TokenType::RIGHT)) 229 | tokens.push_back({std::move(token), TokenType::RIGHT}); 230 | else if (_match(match, TokenType::SEPARATOR)) 231 | tokens.push_back({std::move(token), TokenType::SEPARATOR}); 232 | 233 | string = match.suffix().str(); 234 | last = tokens.back().type; 235 | } 236 | return tokens; 237 | } 238 | 239 | public: 240 | BaseLexer( 241 | std::string num, std::string nam, std::string sgn, 242 | std::string lft, std::string rgt, std::string sep 243 | ) : 244 | number{_adapt_regex(std::move(num))}, 245 | name{_adapt_regex(std::move(nam))}, 246 | sign{_adapt_regex(std::move(sgn))}, 247 | number_regex{number}, 248 | name_regex{name}, 249 | sign_regex{sign}, 250 | left{std::move(lft)}, 251 | right{std::move(rgt)}, 252 | separator{std::move(sep)}, 253 | _splitter_regex{sign.substr(1, sign.size() - 2)}, 254 | _tokenizer_regex{_generate_tokenizer()} 255 | { 256 | std::smatch match{}; 257 | 258 | if (left == right || left == separator || right == separator) 259 | throw LexerError{"tokens must be different"}; 260 | 261 | if (std::regex_match(" ", _tokenizer_regex)) 262 | throw LexerError{"tokenizer matching space"}; 263 | std::regex_search(left, match, _tokenizer_regex); 264 | if (!_match(match, TokenType::LEFT)) 265 | throw LexerError{"tokenizer doesn't match left symbol"}; 266 | std::regex_search(right, match, _tokenizer_regex); 267 | if (!_match(match, TokenType::RIGHT)) 268 | throw LexerError{"tokenizer doesn't match right symbol"}; 269 | std::regex_search(separator, match, _tokenizer_regex); 270 | if (!_match(match, TokenType::SEPARATOR)) 271 | throw LexerError{"tokenizer doesn't match separator symbol"}; 272 | } 273 | 274 | BaseLexer(const BaseLexer&) = default; 275 | BaseLexer(BaseLexer&&) = default; 276 | virtual ~BaseLexer() = default; 277 | 278 | BaseLexer& operator=(const BaseLexer&) = delete; 279 | BaseLexer& operator=(BaseLexer&&) = delete; 280 | 281 | template 282 | inline auto tokenize_infix(Arg&& arg) const { 283 | return _tokenize(std::forward(arg)); 284 | } 285 | 286 | template 287 | inline auto tokenize_postfix(Arg&& arg) const { 288 | return _tokenize(std::forward(arg)); 289 | } 290 | 291 | bool prefixed(const std::string& token) const noexcept { 292 | std::sregex_token_iterator 293 | num{token.begin(), token.end(), _splitter_regex, -1}, 294 | sgn{token.begin(), token.end(), _splitter_regex}; 295 | 296 | return num->str().empty(); 297 | }; 298 | 299 | PrefixedValue split(const std::string& token) const noexcept { 300 | std::sregex_token_iterator 301 | num{token.begin(), token.end(), _splitter_regex, -1}, 302 | sgn{token.begin(), token.end(), _splitter_regex}, 303 | end{}; 304 | 305 | if (sgn == end || num == end || !num->str().empty() || ++num == end) 306 | return {"", ""}; 307 | return {*sgn, *num}; 308 | } 309 | 310 | virtual std::shared_ptr clone() const noexcept = 0; 311 | virtual Type to_value(const std::string&) const = 0; 312 | virtual std::string to_string(const Type&) const = 0; 313 | }; 314 | 315 | 316 | template 317 | class Lexer final : public BaseLexer { 318 | using BaseLexer = calculate::BaseLexer; 319 | 320 | mutable std::istringstream _istream; 321 | mutable std::ostringstream _ostream; 322 | 323 | public: 324 | Lexer( 325 | std::string num, std::string nam, std::string sgn, 326 | std::string lft, std::string rgt, std::string sep 327 | ) : 328 | BaseLexer{ 329 | std::move(num), std::move(nam), std::move(sgn), 330 | std::move(lft), std::move(rgt), std::move(sep) 331 | }, 332 | _istream{}, 333 | _ostream{} 334 | { 335 | _istream.imbue(std::locale("C")); 336 | _ostream.imbue(std::locale("C")); 337 | _ostream << std::setprecision(std::numeric_limits::max_digits10); 338 | } 339 | 340 | Lexer(const Lexer& other) : 341 | BaseLexer(other), 342 | _istream{}, 343 | _ostream{} 344 | { 345 | _istream.imbue(std::locale("C")); 346 | _ostream.imbue(std::locale("C")); 347 | _ostream << std::setprecision(std::numeric_limits::max_digits10); 348 | } 349 | 350 | std::shared_ptr clone() const noexcept override { 351 | return std::make_shared(*this); 352 | } 353 | 354 | Type to_value(const std::string& token) const override { 355 | if (!std::regex_match(token, this->number_regex)) 356 | throw BadCast{token}; 357 | return detail::read(_istream, token); 358 | } 359 | 360 | std::string to_string(const Type& value) const override { 361 | return detail::write(_ostream, value); 362 | } 363 | }; 364 | 365 | template 366 | class Lexer> final : public BaseLexer> { 367 | using BaseLexer = calculate::BaseLexer>; 368 | 369 | static constexpr Type _zero = static_cast(0); 370 | 371 | mutable std::istringstream _istream; 372 | mutable std::ostringstream _ostream; 373 | 374 | const std::regex _splitter; 375 | 376 | public: 377 | Lexer( 378 | std::string num, std::string nam, std::string sgn, 379 | std::string lft, std::string rgt, std::string sep 380 | ) : 381 | BaseLexer{ 382 | std::move(num), std::move(nam), std::move(sgn), 383 | std::move(lft), std::move(rgt), std::move(sep) 384 | }, 385 | _istream{}, 386 | _ostream{}, 387 | _splitter{detail::split} 388 | { 389 | _istream.imbue(std::locale("C")); 390 | _ostream.imbue(std::locale("C")); 391 | _ostream << std::setprecision(std::numeric_limits::max_digits10); 392 | } 393 | 394 | Lexer(const Lexer& other) : 395 | BaseLexer(other), 396 | _istream{}, 397 | _ostream{}, 398 | _splitter{detail::split} 399 | { 400 | _istream.imbue(std::locale("C")); 401 | _ostream.imbue(std::locale("C")); 402 | _ostream << std::setprecision(std::numeric_limits::max_digits10); 403 | } 404 | 405 | std::shared_ptr clone() const noexcept override { 406 | return std::make_shared(*this); 407 | } 408 | 409 | std::complex to_value(const std::string& token) const override { 410 | std::smatch match{}; 411 | 412 | if (!std::regex_search(token, this->number_regex)) 413 | throw BadCast{token}; 414 | 415 | std::regex_search(token, match, _splitter); 416 | if (!match[3].str().empty()) 417 | return {detail::read(_istream, match[3].str()), Type{}}; 418 | if (!match[4].str().empty()) 419 | return {Type{}, detail::read(_istream, match[4].str())}; 420 | return { 421 | detail::read(_istream, match[1].str()), 422 | detail::read(_istream, match[2].str()) 423 | }; 424 | } 425 | 426 | std::string to_string(const std::complex& value) const override { 427 | Type real{std::real(value)}, imag{std::imag(value)}; 428 | std::string token; 429 | 430 | if (real != _zero) 431 | token += 432 | detail::write(_ostream, real) + 433 | (imag > _zero && std::isfinite(imag) ? "+" : ""); 434 | if (real == _zero || imag != _zero) 435 | token += 436 | detail::write(_ostream, imag) + 437 | (real != _zero || imag != _zero ? "j" : ""); 438 | return token; 439 | } 440 | }; 441 | 442 | 443 | template 444 | Lexer make_lexer() noexcept { 445 | using namespace defaults; 446 | return {number, name, sign, left, right, separator}; 447 | } 448 | 449 | } 450 | 451 | 452 | namespace std { 453 | 454 | template 455 | struct hash> { 456 | size_t operator()(const std::complex& z) const { 457 | size_t combined{hash{}(real(z))}; 458 | calculate::util::hash_combine(combined, imag(z)); 459 | return combined; 460 | } 461 | }; 462 | 463 | } 464 | 465 | #endif 466 | -------------------------------------------------------------------------------- /include/calculate/node.hpp: -------------------------------------------------------------------------------- 1 | /* 2 | Calculate - Version 2.1.1rc10 3 | Last modified 2018/07/28 4 | Released under MIT license 5 | Copyright (c) 2016-2018 Alberto Lorenzo 6 | */ 7 | 8 | 9 | #ifndef __CALCULATE_NODE_HPP__ 10 | #define __CALCULATE_NODE_HPP__ 11 | 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | 18 | #include "symbol.hpp" 19 | 20 | 21 | namespace calculate { 22 | 23 | template 24 | class Node { 25 | friend struct std::hash; 26 | friend calculate::Symbol; 27 | friend Parser; 28 | 29 | public: 30 | using Lexer = typename Parser::Lexer; 31 | using Type = typename Parser::Type; 32 | 33 | using Symbol = calculate::Symbol; 34 | using SymbolType = typename Symbol::SymbolType; 35 | 36 | using const_iterator = typename std::vector::const_iterator; 37 | using const_reverse_iterator = typename std::vector::const_reverse_iterator; 38 | 39 | 40 | class VariableHandler { 41 | public: 42 | const std::vector variables; 43 | 44 | private: 45 | std::size_t _size; 46 | std::unique_ptr _values; 47 | std::size_t _refcount; 48 | std::shared_ptr _copy; 49 | 50 | void _update(std::size_t) const {} 51 | 52 | template 53 | void _update(std::size_t n, Last last) { _values[n] = last; } 54 | 55 | template 56 | void _update(std::size_t n, Head head, Args... args) { 57 | _values[n] = head; 58 | _update(n + 1, args...); 59 | } 60 | 61 | public: 62 | VariableHandler(std::vector keys, Lexer* lexer) : 63 | variables{std::move(keys)}, 64 | _size{variables.size()}, 65 | _values{std::make_unique(_size)}, 66 | _refcount{0u}, 67 | _copy{nullptr} 68 | { 69 | std::unordered_set singles{variables.begin(), variables.end()}; 70 | 71 | for (const auto &var : variables) { 72 | if (!std::regex_match(var, lexer->name_regex)) 73 | throw UnsuitableName{var}; 74 | else if (singles.erase(var) == 0) 75 | throw RepeatedSymbol{var}; 76 | } 77 | } 78 | 79 | explicit VariableHandler(std::vector keys) : 80 | variables{std::move(keys)}, 81 | _size{variables.size()}, 82 | _values{std::make_unique(_size)}, 83 | _refcount{0u}, 84 | _copy{nullptr} 85 | {} 86 | 87 | std::shared_ptr rebuild(std::vector keys) noexcept { 88 | ++_refcount; 89 | if (_copy) 90 | return _copy; 91 | _copy = std::make_shared(std::move(keys)); 92 | return _copy; 93 | } 94 | 95 | void reset() noexcept { 96 | if (!--_refcount) 97 | _copy = nullptr; 98 | } 99 | 100 | std::size_t index(const std::string& token) const { 101 | auto found = std::find(variables.begin(), variables.end(), token); 102 | if (found != variables.end()) 103 | return found - variables.begin(); 104 | throw UndefinedSymbol{token}; 105 | } 106 | 107 | Type& at(const std::string& token) const { return _values[index(token)]; } 108 | 109 | template 110 | std::enable_if_t> update(Args&& vals) { 111 | std::size_t i{}; 112 | 113 | for (const auto& val : vals) { 114 | if (i < _size) 115 | _values[i] = val; 116 | ++i; 117 | } 118 | if (_size != i) 119 | throw ArgumentsMismatch{_size, i}; 120 | } 121 | 122 | template 123 | void update(Args&&... vals) { 124 | if (_size != sizeof...(vals)) 125 | throw ArgumentsMismatch{_size, sizeof...(vals)}; 126 | _update(0, std::forward(vals)...); 127 | } 128 | }; 129 | 130 | 131 | private: 132 | std::shared_ptr _lexer; 133 | std::shared_ptr _variables; 134 | std::string _token; 135 | std::unique_ptr _symbol; 136 | std::vector _nodes; 137 | std::size_t _hash; 138 | 139 | Node( 140 | std::shared_ptr _lexer, 141 | std::shared_ptr variables, 142 | std::string token, 143 | std::unique_ptr&& symbol, 144 | std::vector&& nodes, 145 | std::size_t hash 146 | ) : 147 | _lexer{std::move(_lexer)}, 148 | _variables{std::move(variables)}, 149 | _token{std::move(token)}, 150 | _symbol{std::move(symbol)}, 151 | _nodes{std::move(nodes)}, 152 | _hash{hash} 153 | { 154 | if (_nodes.size() != _symbol->arguments()) 155 | throw ArgumentsMismatch{_token, _nodes.size(), _symbol->arguments()}; 156 | } 157 | 158 | std::vector _pruned() const noexcept { 159 | std::vector pruned{}; 160 | 161 | auto tokens = _lexer->tokenize_postfix(postfix()); 162 | for (const auto& var : _variables->variables) 163 | for (const auto& token : tokens) 164 | if (var == token.token) { 165 | pruned.push_back(var); 166 | break; 167 | } 168 | return pruned; 169 | } 170 | 171 | bool _compare(const Node& other) const noexcept { 172 | std::stack these{}, those{}, endings{}; 173 | 174 | auto equal = [&](auto left, auto right) { 175 | try { 176 | return 177 | left->_variables->index(left->_token) == 178 | right->_variables->index(right->_token); 179 | } 180 | catch (const UndefinedSymbol&) { 181 | if (left->_symbol == right->_symbol) 182 | return true; 183 | return *(left->_symbol) == *(right->_symbol); 184 | } 185 | }; 186 | 187 | if (!equal(this, &other)) 188 | return false; 189 | 190 | these.push(this->begin()); 191 | those.push(other.begin()); 192 | endings.push(this->end()); 193 | while(!these.empty()) { 194 | auto &one = these.top(), &another = those.top(); 195 | if (one != endings.top()) { 196 | if (!equal(one, another)) 197 | return false; 198 | these.push(one->begin()); 199 | those.push(another->begin()); 200 | endings.push(one->end()); 201 | one++, another++; 202 | continue; 203 | } 204 | these.pop(); 205 | those.pop(); 206 | endings.pop(); 207 | } 208 | return true; 209 | } 210 | 211 | std::string _infix(bool right) const noexcept { 212 | using Operator = Operator; 213 | using Associativity = typename Operator::Associativity; 214 | 215 | std::string infix{}; 216 | 217 | auto brace = [&](std::size_t i) { 218 | const auto& node = _nodes[i]; 219 | auto po = static_cast(_symbol.get()); 220 | if (node._symbol->type() == SymbolType::OPERATOR) { 221 | auto co = static_cast(node._symbol.get()); 222 | auto pp = po->precedence(); 223 | auto cp = co->precedence(); 224 | auto pa = !i ? 225 | po->associativity() != Associativity::RIGHT : 226 | po->associativity() != Associativity::LEFT; 227 | if ((pa && cp < pp) || (!pa && cp <= pp)) 228 | return _lexer->left + node._infix(false) + _lexer->right; 229 | } 230 | auto r = right || i || po->associativity() == Associativity::RIGHT; 231 | return node._infix(r); 232 | }; 233 | 234 | switch (_symbol->type()) { 235 | case (SymbolType::FUNCTION): 236 | infix += _token + _lexer->left; 237 | for (auto node = _nodes.begin(); node != _nodes.end(); node++) { 238 | infix += node->_infix(false); 239 | infix += (node + 1 != _nodes.end() ? _lexer->separator : ""); 240 | } 241 | return infix + _lexer->right; 242 | 243 | case (SymbolType::OPERATOR): 244 | return infix + brace(0) + _token + brace(1); 245 | 246 | default: 247 | if (right && _lexer->prefixed(_token)) 248 | return _lexer->left + _token + _lexer->right; 249 | return _token; 250 | } 251 | return infix; 252 | } 253 | 254 | public: 255 | Node(const Node& other) noexcept : 256 | _lexer{other._lexer}, 257 | _variables{other._variables->rebuild(other._pruned())}, 258 | _token{other._token}, 259 | _symbol{nullptr}, 260 | _nodes{other._nodes}, 261 | _hash{other._hash} 262 | { 263 | using Variable = Variable; 264 | 265 | other._variables->reset(); 266 | try { 267 | _symbol = Variable{_variables->at(_token)}.clone(); 268 | } 269 | catch (const UndefinedSymbol&) { 270 | _symbol = other._symbol->clone(); 271 | } 272 | } 273 | 274 | Node(Node&& other) noexcept : 275 | _lexer{std::move(other._lexer)}, 276 | _variables{std::move(other._variables)}, 277 | _token{std::move(other._token)}, 278 | _symbol{std::move(other._symbol)}, 279 | _nodes{std::move(other._nodes)}, 280 | _hash{std::move(other._hash)} 281 | {} 282 | 283 | const Node& operator=(Node other) noexcept { 284 | swap(*this, other); 285 | return *this; 286 | } 287 | 288 | friend void swap(Node& one, Node& another) noexcept { 289 | using std::swap; 290 | swap(one._lexer, another._lexer); 291 | swap(one._variables, another._variables); 292 | swap(one._token, another._token); 293 | swap(one._symbol, another._symbol); 294 | swap(one._nodes, another._nodes); 295 | } 296 | 297 | operator Type() const { 298 | if (_variables->variables.size() > 0) 299 | throw ArgumentsMismatch{_variables->variables.size(), 0}; 300 | return _symbol->eval(_nodes); 301 | } 302 | 303 | template 304 | Type operator()(Args&&... args) const { 305 | _variables->update(std::forward(args)...); 306 | return _symbol->eval(_nodes); 307 | } 308 | 309 | bool operator==(const Node& other) const noexcept { 310 | if (_hash != other._hash) 311 | return false; 312 | return _compare(other); 313 | } 314 | 315 | bool operator!=(const Node& other) const noexcept { return !operator==(other); } 316 | 317 | const Node& operator[](std::size_t index) const { return _nodes[index]; } 318 | 319 | const Node& at(std::size_t index) const { return _nodes.at(index); } 320 | 321 | std::size_t size() const noexcept { return _nodes.size(); } 322 | 323 | const_iterator begin() const noexcept { return _nodes.cbegin(); } 324 | 325 | const_iterator end() const noexcept { return _nodes.cend(); } 326 | 327 | const_iterator cbegin() const noexcept { return _nodes.cbegin(); } 328 | 329 | const_iterator cend() const noexcept { return _nodes.cend(); } 330 | 331 | const_reverse_iterator rbegin() const noexcept { return _nodes.crbegin(); } 332 | 333 | const_reverse_iterator rend() const noexcept { return _nodes.crend(); } 334 | 335 | const_reverse_iterator crbegin() const noexcept { return _nodes.crbegin(); } 336 | 337 | const_reverse_iterator crend() const noexcept { return _nodes.crend(); } 338 | 339 | friend std::ostream& operator<<(std::ostream& ostream, const Node& node) noexcept { 340 | ostream << node.infix(); 341 | return ostream; 342 | } 343 | 344 | const Lexer& lexer() const noexcept { return *_lexer; } 345 | 346 | const std::string& token() const noexcept { return _token; } 347 | 348 | SymbolType type() const noexcept { return _symbol->type(); } 349 | 350 | std::size_t branches() const noexcept { return _nodes.size(); } 351 | 352 | std::string infix() const noexcept { return _infix(false); } 353 | 354 | std::string postfix() const noexcept { 355 | std::string postfix{}; 356 | 357 | for (const auto& node : _nodes) 358 | postfix += node.postfix() + " "; 359 | return postfix + _token; 360 | } 361 | 362 | const std::vector& variables() const noexcept { return _variables->variables; } 363 | }; 364 | 365 | } 366 | 367 | 368 | namespace std { 369 | 370 | template 371 | struct hash> { 372 | size_t operator()(const calculate::Node& node) const { return node._hash; } 373 | }; 374 | 375 | } 376 | 377 | #endif 378 | -------------------------------------------------------------------------------- /include/calculate/parser.hpp: -------------------------------------------------------------------------------- 1 | /* 2 | Calculate - Version 2.1.1rc10 3 | Last modified 2018/08/28 4 | Released under MIT license 5 | Copyright (c) 2016-2018 Alberto Lorenzo 6 | */ 7 | 8 | 9 | #ifndef __CALCULATE_PARSER_HPP__ 10 | #define __CALCULATE_PARSER_HPP__ 11 | 12 | #include 13 | #include 14 | 15 | #include "container.hpp" 16 | #include "lexer.hpp" 17 | #include "node.hpp" 18 | 19 | 20 | namespace calculate { 21 | 22 | template 23 | class BaseParser { 24 | public: 25 | using Lexer = calculate::BaseLexer; 26 | using Type = BaseType; 27 | 28 | using Expression = calculate::Node; 29 | using Symbol = calculate::Symbol; 30 | using Constant = calculate::Constant; 31 | using Function = calculate::Function; 32 | using Operator = calculate::Operator; 33 | 34 | private: using VariableHandler = typename Expression::VariableHandler; 35 | public: using SymbolType = typename Symbol::SymbolType; 36 | public: using Associativity = typename Operator::Associativity; 37 | 38 | 39 | private: 40 | std::shared_ptr _lexer; 41 | 42 | public: 43 | SymbolContainer constants; 44 | SymbolContainer functions; 45 | SymbolContainer operators; 46 | AliasContainer prefixes; 47 | AliasContainer suffixes; 48 | bool optimize; 49 | 50 | BaseParser(const Lexer& lexer) : 51 | _lexer{lexer.clone()}, 52 | constants{_lexer.get()}, 53 | functions{_lexer.get()}, 54 | operators{_lexer.get()}, 55 | prefixes{_lexer.get()}, 56 | suffixes{_lexer.get()}, 57 | optimize{false} 58 | {} 59 | 60 | const Lexer& lexer() const noexcept { return *_lexer; } 61 | 62 | 63 | private: 64 | struct SymbolData { 65 | std::string token; 66 | SymbolType type; 67 | std::unique_ptr symbol; 68 | }; 69 | 70 | SymbolData _left() const noexcept { return {_lexer->left, SymbolType::LEFT, nullptr}; } 71 | 72 | SymbolData _right() const noexcept { return {_lexer->right, SymbolType::RIGHT, nullptr}; } 73 | 74 | 75 | template 76 | std::queue _tokenize(const std::string& expr, VariableHandler* variables) const { 77 | using Associativity = typename Operator::Associativity; 78 | using TokenType = typename Lexer::TokenType; 79 | 80 | std::queue symbols{}; 81 | decltype(constants.begin()) con; 82 | decltype(functions.begin()) fun; 83 | decltype(operators.begin()) ope; 84 | decltype(prefixes.begin()) sym; 85 | 86 | auto has = [](const auto& cont, const auto& token, auto& it) noexcept { 87 | return (it = cont.find(token)) != cont.end(); 88 | }; 89 | 90 | auto push_operator = [&](const auto& token) { 91 | auto previous = symbols.empty() ? SymbolType::LEFT : symbols.back().type; 92 | auto leftmost = 93 | previous == SymbolType::LEFT || 94 | previous == SymbolType::SEPARATOR || 95 | previous == SymbolType::OPERATOR || 96 | previous == SymbolType::PREFIX; 97 | 98 | if (infix && leftmost && has(prefixes, token, sym) && has(functions, sym->second, fun)) 99 | symbols.push({ 100 | std::move(sym->second), 101 | SymbolType::PREFIX, 102 | fun->second.clone() 103 | }); 104 | else if (infix && has(suffixes, token, sym) && has(functions, sym->second, fun)) 105 | symbols.push({ 106 | std::move(sym->second), 107 | SymbolType::SUFFIX, 108 | fun->second.clone() 109 | }); 110 | else if (has(operators, token, ope)) 111 | symbols.push({ 112 | std::move(token), 113 | SymbolType::OPERATOR, 114 | ope->second.clone() 115 | }); 116 | else 117 | throw UndefinedSymbol{token}; 118 | }; 119 | 120 | auto tokens = infix ? _lexer->tokenize_infix(expr) : _lexer->tokenize_postfix(expr); 121 | for (auto current = tokens.begin(); current != tokens.end(); current++) { 122 | auto next = current + 1; 123 | 124 | if (current->type == TokenType::NUMBER) { 125 | auto straightened = 126 | next != tokens.end() && has(operators, next->token, ope) && 127 | ope->second.associativity() == Associativity::RIGHT; 128 | auto suffixed = 129 | (next != tokens.end() && has(suffixes, next->token, sym)) || 130 | (symbols.size() && symbols.back().type == SymbolType::SUFFIX); 131 | 132 | if (infix && (straightened || suffixed) && _lexer->prefixed(current->token)) { 133 | auto prefixed = _lexer->split(current->token); 134 | push_operator(prefixed.prefix); 135 | symbols.push({ 136 | prefixed.value, 137 | SymbolType::CONSTANT, 138 | Constant{_lexer->to_value(prefixed.value)}.clone() 139 | }); 140 | } 141 | else 142 | symbols.push({ 143 | current->token, 144 | SymbolType::CONSTANT, 145 | Constant{_lexer->to_value(current->token)}.clone() 146 | }); 147 | } 148 | else if (current->type == TokenType::LEFT) 149 | symbols.push(_left()); 150 | else if (current->type == TokenType::RIGHT) 151 | symbols.push(_right()); 152 | else if (current->type == TokenType::SEPARATOR) 153 | symbols.push({_lexer->separator, SymbolType::SEPARATOR, nullptr}); 154 | else if (current->type == TokenType::SIGN) 155 | push_operator(current->token); 156 | else if (current->type == TokenType::NAME && has(constants, current->token, con)) 157 | symbols.push({ 158 | std::move(current->token), 159 | SymbolType::CONSTANT, 160 | con->second.clone() 161 | }); 162 | else if (current->type == TokenType::NAME && has(functions, current->token, fun)) 163 | symbols.push({ 164 | std::move(current->token), 165 | SymbolType::FUNCTION, 166 | fun->second.clone() 167 | }); 168 | else 169 | symbols.push({ 170 | current->token, 171 | SymbolType::CONSTANT, 172 | Variable>{variables->at(current->token)}.clone() 173 | }); 174 | } 175 | 176 | return symbols; 177 | } 178 | 179 | std::queue _parse_infix(std::queue&& symbols) const { 180 | using Associativity = typename Operator::Associativity; 181 | 182 | std::string parsed{}; 183 | std::queue collected{}; 184 | SymbolData previous{_left()}; 185 | SymbolData current{}; 186 | std::stack automatic{}; 187 | 188 | auto fill_parenthesis = [&]() noexcept { 189 | while (!automatic.empty()) { 190 | if (automatic.top()) { 191 | collected.push(_right()); 192 | previous = _right(); 193 | automatic.pop(); 194 | } 195 | else 196 | break; 197 | } 198 | }; 199 | 200 | auto get_symbol = [&](const auto& handler) noexcept { 201 | if (handler.type == SymbolType::PREFIX) { 202 | for (const auto& prefix : prefixes) 203 | if (prefix.second == handler.token) 204 | return prefix.first; 205 | } 206 | else if (handler.type == SymbolType::SUFFIX) { 207 | for (const auto& suffix : suffixes) 208 | if (suffix.second == handler.token) 209 | return suffix.first; 210 | } 211 | return handler.token; 212 | }; 213 | 214 | auto collect_symbol = [&](bool log=true) { 215 | switch (previous.type) { 216 | case (SymbolType::RIGHT): 217 | case (SymbolType::CONSTANT): 218 | case (SymbolType::SUFFIX): 219 | if ( 220 | current.type == SymbolType::RIGHT || 221 | current.type == SymbolType::SEPARATOR || 222 | current.type == SymbolType::OPERATOR || 223 | current.type == SymbolType::SUFFIX 224 | ) 225 | break; 226 | else 227 | throw SyntaxError{}; 228 | case (SymbolType::LEFT): 229 | case (SymbolType::SEPARATOR): 230 | case (SymbolType::OPERATOR): 231 | if ( 232 | current.type == SymbolType::CONSTANT || 233 | current.type == SymbolType::LEFT || 234 | current.type == SymbolType::FUNCTION || 235 | current.type == SymbolType::PREFIX 236 | ) 237 | break; 238 | else 239 | throw SyntaxError{}; 240 | case (SymbolType::FUNCTION): 241 | case (SymbolType::PREFIX): 242 | if (current.type == SymbolType::LEFT) 243 | break; 244 | else 245 | throw SyntaxError{}; 246 | } 247 | 248 | if ( 249 | previous.type == SymbolType::RIGHT || 250 | previous.type == SymbolType::CONSTANT || 251 | previous.type == SymbolType::SUFFIX 252 | ) { 253 | auto co = static_cast(current.symbol.get()); 254 | auto not_right = 255 | current.type != SymbolType::OPERATOR || 256 | co->associativity() != Associativity::RIGHT; 257 | not_right = not_right && current.type != SymbolType::SUFFIX; 258 | if (not_right) 259 | fill_parenthesis(); 260 | } 261 | 262 | if (log && current.type == SymbolType::LEFT) 263 | automatic.push(false); 264 | else if (current.type == SymbolType::RIGHT) 265 | if (!automatic.empty() && !automatic.top()) 266 | automatic.pop(); 267 | 268 | if (log) 269 | parsed += get_symbol(current) + " "; 270 | previous = {current.token, current.type, nullptr}; 271 | collected.push(std::move(current)); 272 | }; 273 | 274 | if (symbols.size() == 0) 275 | throw EmptyExpression{}; 276 | 277 | try { 278 | while (!symbols.empty()) { 279 | current = std::move(symbols.front()); 280 | symbols.pop(); 281 | 282 | if (current.type != SymbolType::PREFIX) 283 | collect_symbol(); 284 | else { 285 | if (symbols.empty()) 286 | throw SyntaxError{}; 287 | collect_symbol(true); 288 | current = _left(); 289 | automatic.push(true); 290 | collect_symbol(false); 291 | } 292 | } 293 | 294 | if ( 295 | previous.type == SymbolType::RIGHT || 296 | previous.type == SymbolType::CONSTANT || 297 | previous.type == SymbolType::SUFFIX 298 | ) 299 | fill_parenthesis(); 300 | else 301 | throw SyntaxError{}; 302 | } 303 | catch (const SyntaxError&) { 304 | parsed +="'" + get_symbol(current) + "' "; 305 | while (!symbols.empty()) { 306 | current = std::move(symbols.front()); 307 | symbols.pop(); 308 | parsed += get_symbol(current) + " "; 309 | } 310 | 311 | if (current.token.empty()) { 312 | auto n = parsed.rfind(' ', parsed.size() - 5); 313 | parsed = 314 | (n == std::string::npos ? "" : 315 | parsed.substr(0, n) + " ") + "'" + get_symbol(previous) + "'"; 316 | } 317 | else 318 | parsed = parsed.substr(0, parsed.size() - 1); 319 | throw SyntaxError{parsed}; 320 | } 321 | 322 | return collected; 323 | } 324 | 325 | std::queue _shunting_yard(std::queue&& symbols) const { 326 | using Associativity = typename Operator::Associativity; 327 | 328 | std::queue collected{}; 329 | std::stack operations{}; 330 | SymbolData element{}; 331 | SymbolData another{}; 332 | 333 | std::stack expected_counter{}; 334 | std::stack provided_counter{}; 335 | std::stack apply_function{}; 336 | bool was_function{false}; 337 | 338 | if (symbols.size() == 0) 339 | throw EmptyExpression{}; 340 | 341 | while (!symbols.empty()) { 342 | element = std::move(symbols.front()); 343 | symbols.pop(); 344 | 345 | switch (element.type) { 346 | case (SymbolType::LEFT): 347 | operations.push(std::move(element)); 348 | apply_function.push(was_function); 349 | was_function = false; 350 | break; 351 | 352 | case (SymbolType::RIGHT): 353 | while (!operations.empty()) { 354 | if (operations.top().type != SymbolType::LEFT) { 355 | collected.push(std::move(operations.top())); 356 | operations.pop(); 357 | } 358 | else 359 | break; 360 | } 361 | 362 | if (!operations.empty() && operations.top().type == SymbolType::LEFT) 363 | operations.pop(); 364 | else 365 | throw ParenthesisMismatch{}; 366 | 367 | if (apply_function.top()) { 368 | if (expected_counter.top() != provided_counter.top()) 369 | throw ArgumentsMismatch{ 370 | operations.top().token, 371 | expected_counter.top(), 372 | provided_counter.top() 373 | }; 374 | collected.push(std::move(operations.top())); 375 | operations.pop(); 376 | expected_counter.pop(); 377 | provided_counter.pop(); 378 | } 379 | apply_function.pop(); 380 | break; 381 | 382 | case (SymbolType::SEPARATOR): 383 | while (!operations.empty()) { 384 | if (operations.top().type != SymbolType::LEFT) { 385 | collected.push(std::move(operations.top())); 386 | operations.pop(); 387 | } 388 | else 389 | break; 390 | } 391 | 392 | if (apply_function.empty() || !apply_function.top()) 393 | throw SyntaxError{"separator '" + element.token + "' outside function"}; 394 | provided_counter.top()++; 395 | 396 | if (operations.empty()) 397 | throw ParenthesisMismatch{}; 398 | break; 399 | 400 | case (SymbolType::CONSTANT): 401 | case (SymbolType::SUFFIX): 402 | collected.push(std::move(element)); 403 | break; 404 | 405 | case (SymbolType::FUNCTION): 406 | case (SymbolType::PREFIX): 407 | expected_counter.push(element.symbol->arguments()); 408 | provided_counter.push(1); 409 | was_function = true; 410 | operations.push(std::move(element)); 411 | break; 412 | 413 | case (SymbolType::OPERATOR): 414 | while (!operations.empty()) { 415 | auto& another = operations.top(); 416 | if (another.type == SymbolType::LEFT) 417 | break; 418 | else if ( 419 | another.type == SymbolType::FUNCTION || 420 | another.type == SymbolType::PREFIX 421 | ) { 422 | collected.push(std::move(another)); 423 | operations.pop(); 424 | break; 425 | } 426 | else { 427 | auto eo = static_cast(element.symbol.get()); 428 | auto ao = static_cast(another.symbol.get()); 429 | auto l1 = eo->associativity() != Associativity::RIGHT; 430 | auto p1 = eo->precedence(); 431 | auto p2 = ao->precedence(); 432 | if ((l1 && (p1 <= p2)) || (!l1 && (p1 < p2))) { 433 | collected.push(std::move(another)); 434 | operations.pop(); 435 | } 436 | else 437 | break; 438 | } 439 | } 440 | operations.push(std::move(element)); 441 | break; 442 | } 443 | } 444 | 445 | while (!operations.empty()) { 446 | element = std::move(operations.top()); 447 | operations.pop(); 448 | if (element.type == SymbolType::LEFT) 449 | throw ParenthesisMismatch{}; 450 | collected.push(std::move(element)); 451 | } 452 | 453 | return collected; 454 | } 455 | 456 | Expression _build_tree( 457 | std::queue&& symbols, 458 | std::shared_ptr&& variables 459 | ) const { 460 | std::stack operands{}; 461 | std::stack extract{}; 462 | SymbolData element{}; 463 | 464 | while (!symbols.empty()) { 465 | std::vector nodes{}; 466 | std::unique_ptr symbol{}; 467 | std::string token{}; 468 | std::size_t hash{}; 469 | bool collapse{optimize}; 470 | 471 | element = std::move(symbols.front()); 472 | symbols.pop(); 473 | 474 | if ( 475 | element.type == SymbolType::LEFT || 476 | element.type == SymbolType::RIGHT || 477 | element.type == SymbolType::SEPARATOR 478 | ) 479 | throw SyntaxError{"'" + element.token + "' not allowed in postfix notation"}; 480 | 481 | if (element.type != SymbolType::CONSTANT) { 482 | nodes.reserve(element.symbol->arguments()); 483 | for (std::size_t i = 0; i < element.symbol->arguments(); i++) { 484 | if (operands.empty()) 485 | throw ArgumentsMismatch{element.token, element.symbol->arguments(), i}; 486 | collapse = collapse && operands.top()._pruned().empty(); 487 | util::hash_combine(hash, operands.top()); 488 | extract.emplace(std::move(operands.top())); 489 | operands.pop(); 490 | } 491 | while (!extract.empty()) { 492 | nodes.emplace_back(std::move(extract.top())); 493 | extract.pop(); 494 | } 495 | } 496 | else 497 | collapse = false; 498 | 499 | util::hash_combine(hash, *(element.symbol)); 500 | if (collapse) { 501 | symbol = Constant{element.symbol->eval(nodes)}.clone(); 502 | token = _lexer->to_string(symbol->eval()); 503 | nodes.clear(); 504 | hash = std::size_t{}; 505 | util::hash_combine(hash, *symbol); 506 | } 507 | else { 508 | symbol = std::move(element.symbol); 509 | token = std::move(element.token); 510 | } 511 | 512 | operands.emplace( 513 | Expression{ 514 | _lexer, 515 | variables, 516 | std::move(token), 517 | std::move(symbol), 518 | std::move(nodes), 519 | hash 520 | } 521 | ); 522 | } 523 | 524 | if (operands.size() > 1) { 525 | operands.pop(); 526 | throw UnusedSymbol{operands.top().token()}; 527 | } 528 | 529 | auto pruned = operands.top()._pruned(); 530 | for (const auto& var : variables->variables) 531 | if (std::find(pruned.begin(), pruned.end(), var) == pruned.end()) 532 | throw UnusedSymbol(var); 533 | 534 | return std::move(operands.top()); 535 | } 536 | 537 | 538 | public: 539 | template 540 | Expression from_infix(const std::string& expr, Args&&... vars) const { 541 | auto variables = std::make_shared( 542 | util::to_vector(std::forward(vars)...), 543 | _lexer.get() 544 | ); 545 | auto symbols = _shunting_yard(_parse_infix(_tokenize(expr, variables.get()))); 546 | 547 | return _build_tree(std::move(symbols), std::move(variables)); 548 | } 549 | 550 | template 551 | Expression from_postfix(const std::string& expr, Args&&... vars) const { 552 | auto variables = std::make_shared( 553 | util::to_vector(std::forward(vars)...), 554 | _lexer.get() 555 | ); 556 | auto symbols = _tokenize(expr, variables.get()); 557 | 558 | return _build_tree(std::move(symbols), std::move(variables)); 559 | } 560 | 561 | Expression parse(const std::string& expr) const { 562 | std::vector vars{}; 563 | 564 | while (true) { 565 | try { 566 | return from_infix(expr, vars); 567 | } 568 | catch (const UndefinedSymbol& exception) { 569 | vars.emplace_back(exception.token); 570 | } 571 | catch (const UnsuitableName& exception) { 572 | throw UndefinedSymbol{exception.token}; 573 | } 574 | } 575 | } 576 | }; 577 | 578 | } 579 | 580 | #endif 581 | -------------------------------------------------------------------------------- /include/calculate/symbol.hpp: -------------------------------------------------------------------------------- 1 | /* 2 | Calculate - Version 2.1.1rc10 3 | Last modified 2018/09/04 4 | Released under MIT license 5 | Copyright (c) 2016-2018 Alberto Lorenzo 6 | */ 7 | 8 | 9 | #ifndef __CALCULATE_SYMBOL_HPP__ 10 | #define __CALCULATE_SYMBOL_HPP__ 11 | 12 | #include "wrapper.hpp" 13 | 14 | 15 | namespace calculate { 16 | 17 | template class Function; 18 | template class Operator; 19 | 20 | template 21 | class Symbol : Wrapper { 22 | friend struct std::hash; 23 | 24 | public: 25 | using Type = typename Expression::Type; 26 | 27 | enum class SymbolType { LEFT, RIGHT, SEPARATOR, CONSTANT, FUNCTION, OPERATOR, PREFIX, SUFFIX }; 28 | 29 | private: 30 | using WrapperConcept = calculate::WrapperConcept; 31 | using Wrapper = calculate::Wrapper; 32 | 33 | std::size_t _hash() const noexcept { 34 | if (type() == SymbolType::CONSTANT) 35 | return std::hash()(static_cast(*this)()); 36 | return std::hash()(static_cast(*this)); 37 | } 38 | 39 | virtual bool _equal(const Symbol&) const noexcept = 0; 40 | 41 | public: 42 | template< 43 | typename Callable, 44 | typename = std::enable_if_t>, 45 | typename = std::enable_if_t> 46 | > 47 | Symbol(Callable&& callable) : 48 | Wrapper{ 49 | std::forward(callable), 50 | [](const Expression& e) noexcept { return e._symbol->eval(e._nodes); } 51 | } 52 | { 53 | static_assert( 54 | util::not_same_v> || util::argc_v == 0, 55 | "Functions must have one argument at least" 56 | ); 57 | static_assert( 58 | util::not_same_v> || util::argc_v == 2, 59 | "Operators must have two arguments" 60 | ); 61 | } 62 | 63 | template< 64 | typename Callable, 65 | typename = std::enable_if_t> 66 | > 67 | Symbol(Callable&& callable) : Wrapper{std::forward(callable)} {} 68 | 69 | virtual ~Symbol() = default; 70 | 71 | template 72 | bool operator==(const Class& other) const noexcept { 73 | auto& this_wrapper = static_cast(*this); 74 | auto& other_wrapper = static_cast(other); 75 | 76 | if (type() != other.type()) 77 | return false; 78 | if (type() != SymbolType::CONSTANT && this_wrapper != other_wrapper) 79 | return false; 80 | return this->_equal(other); 81 | } 82 | 83 | template 84 | bool operator!=(const Class& other) const noexcept { return !operator==(other); } 85 | 86 | using Wrapper::operator(); 87 | 88 | using Wrapper::eval; 89 | 90 | std::size_t arguments() const noexcept { return static_cast(this)->argc(); } 91 | 92 | virtual SymbolType type() const noexcept = 0; 93 | 94 | virtual std::unique_ptr clone() const = 0; 95 | }; 96 | 97 | 98 | template 99 | class Variable final : public Symbol { 100 | using Symbol = calculate::Symbol; 101 | using SymbolType = typename Symbol::SymbolType; 102 | 103 | bool _equal(const Symbol&) const noexcept override { return false; } 104 | 105 | public: 106 | using Type = typename Expression::Type; 107 | 108 | Variable(Type& variable) : 109 | Symbol{[&variable]() noexcept { return variable; }} 110 | {} 111 | 112 | SymbolType type() const noexcept override { return SymbolType::CONSTANT; } 113 | 114 | std::unique_ptr clone() const override { 115 | return std::make_unique(*this); 116 | } 117 | }; 118 | 119 | template 120 | class Constant final : public Symbol { 121 | using Symbol = calculate::Symbol; 122 | using SymbolType = typename Symbol::SymbolType; 123 | 124 | bool _equal(const Symbol& other) const noexcept override { return Type{*this} == other(); } 125 | 126 | public: 127 | using Type = typename Expression::Type; 128 | 129 | Constant(Type value) : 130 | Symbol{[value]() noexcept { return value; }} 131 | {} 132 | 133 | operator Type() const { return Symbol::operator()(); } 134 | 135 | template bool operator==(U value) const { return Type{*this} == value; } 136 | template bool operator!=(U value) const { return Type{*this} != value; } 137 | template bool operator>(U value) const { return Type{*this} > value; } 138 | template bool operator<(U value) const { return Type{*this} < value; } 139 | template bool operator>=(U value) const { return Type{*this} >= value; } 140 | template bool operator<=(U value) const { return Type{*this} <= value; } 141 | 142 | template auto operator+(U value) const { return Type{*this} + value; } 143 | template auto operator-(U value) const { return Type{*this} - value; } 144 | template auto operator*(U value) const { return Type{*this} * value; } 145 | template auto operator/(U value) const { return Type{*this} / value; } 146 | template auto operator%(U value) const { return Type{*this} % value; } 147 | 148 | SymbolType type() const noexcept override { return SymbolType::CONSTANT; } 149 | 150 | std::unique_ptr clone() const override { 151 | return std::make_unique(*this); 152 | } 153 | }; 154 | 155 | template 156 | class Function final : public Symbol { 157 | using Symbol = calculate::Symbol; 158 | using SymbolType = typename Symbol::SymbolType; 159 | 160 | bool _equal(const Symbol&) const noexcept override { return true; } 161 | 162 | public: 163 | using Type = typename Expression::Type; 164 | 165 | template 166 | Function(Callable&& callable) : 167 | Symbol{std::forward(callable)} 168 | {} 169 | 170 | template 171 | Type operator()(Args&&... args) const { 172 | return Symbol::operator()(std::forward(args)...); 173 | } 174 | 175 | SymbolType type() const noexcept override { return SymbolType::FUNCTION; } 176 | 177 | std::unique_ptr clone() const override { 178 | return std::make_unique(*this); 179 | } 180 | }; 181 | 182 | template 183 | class Operator final : public Symbol { 184 | using Symbol = calculate::Symbol; 185 | using SymbolType = typename Symbol::SymbolType; 186 | 187 | public: 188 | using Type = typename Expression::Type; 189 | 190 | enum class Associativity {LEFT, RIGHT, FULL}; 191 | 192 | private: 193 | std::size_t _precedence; 194 | Associativity _associativity; 195 | 196 | bool _equal(const Symbol& other) const noexcept override { 197 | auto op = static_cast(other); 198 | return _precedence == op._precedence && _associativity == op._associativity; 199 | } 200 | 201 | public: 202 | template 203 | Operator( 204 | Callable&& callable, 205 | std::size_t precedence, 206 | Associativity associativity 207 | ) : 208 | Symbol{std::forward(callable)}, 209 | _precedence{precedence}, 210 | _associativity{associativity} 211 | {} 212 | 213 | template 214 | Type operator()(Args&&... args) const { 215 | return Symbol::operator()(std::forward(args)...); 216 | } 217 | 218 | std::size_t precedence() const noexcept { return _precedence; } 219 | 220 | Associativity associativity() const noexcept { return _associativity; } 221 | 222 | SymbolType type() const noexcept override { return SymbolType::OPERATOR; } 223 | 224 | std::unique_ptr clone() const override { 225 | return std::make_unique(*this); 226 | } 227 | }; 228 | 229 | } 230 | 231 | 232 | namespace std { 233 | 234 | template 235 | struct hash> { 236 | size_t operator()(const calculate::Symbol& symbol) const { return symbol._hash(); } 237 | }; 238 | 239 | } 240 | 241 | #endif 242 | -------------------------------------------------------------------------------- /include/calculate/util.hpp: -------------------------------------------------------------------------------- 1 | /* 2 | Calculate - Version 2.1.1rc10 3 | Last modified 2018/09/03 4 | Released under MIT license 5 | Copyright (c) 2016-2018 Alberto Lorenzo 6 | */ 7 | 8 | 9 | #ifndef __CALCULATE_UTIL_HPP__ 10 | #define __CALCULATE_UTIL_HPP__ 11 | 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | 18 | #include "exception.hpp" 19 | 20 | 21 | namespace calculate { 22 | 23 | namespace util { 24 | 25 | template 26 | constexpr bool is_same_v = std::is_same::value; 27 | 28 | template 29 | constexpr bool is_base_of_v = std::is_base_of::value; 30 | 31 | template 32 | constexpr bool is_integral_v = std::is_integral::value; 33 | 34 | template 35 | constexpr bool is_copy_constructible_v = std::is_copy_constructible::value; 36 | 37 | template 38 | constexpr std::size_t tuple_size_v = std::tuple_size::value; 39 | 40 | 41 | template 42 | constexpr bool is_value_v = !std::is_pointer::value && !std::is_reference::value; 43 | 44 | template 45 | constexpr bool is_const_reference_v = 46 | std::is_lvalue_reference::value && 47 | std::is_const>::value; 48 | 49 | template 50 | constexpr bool is_valid_v = is_value_v || is_const_reference_v; 51 | 52 | template 53 | struct decay { 54 | static_assert(is_valid_v, "Arguments may suffer from side effects"); 55 | using type = std::decay_t; 56 | }; 57 | 58 | template 59 | using decay_t = typename decay::type; 60 | 61 | 62 | namespace detail { 63 | 64 | using std::begin; 65 | using std::end; 66 | 67 | template 68 | constexpr decltype( 69 | begin(std::declval()) != end(std::declval()), 70 | ++std::declval()))&>(), 71 | *begin(std::declval()), 72 | bool{} 73 | ) is_iterable(int) { return true; } 74 | 75 | template 76 | constexpr bool is_iterable(...) { return false; } 77 | 78 | template 79 | constexpr decltype(T(*begin(std::declval())), bool{}) is_subtype(int) { return true; } 80 | 81 | template 82 | constexpr bool is_subtype(...) { return false; } 83 | 84 | 85 | template 86 | using extract_type = T; 87 | 88 | template> 89 | struct make_tuple {}; 90 | 91 | template 92 | struct make_tuple> { 93 | using type = std::tuple...>; 94 | }; 95 | 96 | 97 | template 98 | constexpr bool is_noexcept_v = noexcept(std::declval()(std::declval()...)); 99 | 100 | template 101 | struct TraitsHandler { 102 | static constexpr bool is_const_v = c; 103 | using result_t = R; 104 | using args_tuple_t = std::tuple...>; 105 | }; 106 | 107 | template 108 | struct Traits : Traits::operator())> {}; 109 | 110 | template 111 | struct Traits> : TraitsHandler {}; 112 | 113 | template 114 | struct Traits< 115 | R(*)(Args...) noexcept, 116 | std::enable_if_t> 117 | > : TraitsHandler {}; 118 | 119 | template 120 | struct Traits< 121 | R(*)(Args...), 122 | std::enable_if_t> 123 | > : TraitsHandler {}; 124 | 125 | template 126 | struct Traits< 127 | R(&)(Args...) noexcept, 128 | std::enable_if_t> 129 | > : TraitsHandler {}; 130 | 131 | template 132 | struct Traits< 133 | R(&)(Args...), 134 | std::enable_if_t> 135 | > : TraitsHandler {}; 136 | 137 | template 138 | struct Traits< 139 | R(T::*)(Args...) noexcept, 140 | std::enable_if_t> 141 | > : TraitsHandler {}; 142 | 143 | template 144 | struct Traits< 145 | R(T::*)(Args...), 146 | std::enable_if_t> 147 | > : TraitsHandler {}; 148 | 149 | template 150 | struct Traits< 151 | R(T::*)(Args...) const noexcept, 152 | std::enable_if_t> 153 | > : TraitsHandler {}; 154 | 155 | template 156 | struct Traits< 157 | R(T::*)(Args...) const, 158 | std::enable_if_t> 159 | > : TraitsHandler {}; 160 | 161 | } 162 | 163 | template 164 | constexpr bool is_iterable_v = detail::is_iterable(0); 165 | 166 | template 167 | constexpr bool is_subtype_v = detail::is_subtype(0); 168 | 169 | template 170 | constexpr bool is_vectorizable_v = is_iterable_v && is_subtype_v; 171 | 172 | template 173 | constexpr bool is_noexcept_v = detail::is_noexcept_v; 174 | 175 | 176 | template 177 | using make_tuple_t = typename detail::make_tuple::type; 178 | 179 | template 180 | using result_t = typename detail::Traits::result_t; 181 | 182 | template 183 | constexpr bool is_const_v = detail::Traits::is_const_v; 184 | 185 | template 186 | using args_tuple_t = typename detail::Traits::args_tuple_t; 187 | 188 | template 189 | constexpr std::size_t argc_v = tuple_size_v::args_tuple_t>; 190 | 191 | template 192 | constexpr bool not_same_v = !is_same_v, U> && !is_base_of_v>; 193 | 194 | 195 | template 196 | std::vector to_vector(Args&&... args) { 197 | return {static_cast(std::forward(args))...}; 198 | } 199 | 200 | template 201 | std::enable_if_t, std::vector> to_vector(U&& u) { 202 | return {std::begin(std::forward(u)), std::end(std::forward(u))}; 203 | } 204 | 205 | 206 | template 207 | void hash_combine(std::size_t& seed, const T& object) { 208 | std::hash hasher; 209 | seed ^= hasher(object) + 0x9e3779b9 + (seed << 6) + (seed >> 2); 210 | } 211 | 212 | } 213 | 214 | } 215 | 216 | #endif 217 | -------------------------------------------------------------------------------- /include/calculate/wrapper.hpp: -------------------------------------------------------------------------------- 1 | /* 2 | Calculate - Version 2.1.1rc10 3 | Last modified 2018/09/05 4 | Released under MIT license 5 | Copyright (c) 2016-2018 Alberto Lorenzo 6 | */ 7 | 8 | 9 | #ifndef __CALCULATE_WRAPPER_HPP__ 10 | #define __CALCULATE_WRAPPER_HPP__ 11 | 12 | #include 13 | 14 | #include "util.hpp" 15 | 16 | 17 | namespace calculate { 18 | 19 | template 20 | struct WrapperConcept { 21 | virtual ~WrapperConcept() = default; 22 | virtual std::shared_ptr clone() const = 0; 23 | virtual std::size_t argc() const noexcept = 0; 24 | virtual Type eval(const std::vector&) const = 0; 25 | virtual Type call(const std::vector&) const = 0; 26 | }; 27 | 28 | 29 | template 30 | class Wrapper { 31 | static_assert(util::is_value_v, "Wrapper type is not a value"); 32 | 33 | friend struct std::hash; 34 | using WrapperConcept = calculate::WrapperConcept; 35 | 36 | template 37 | class WrapperModel final : public WrapperConcept { 38 | static_assert( 39 | util::is_same_v< 40 | util::args_tuple_t, 41 | util::make_tuple_t> 42 | >, 43 | "Wrong callable arguments types" 44 | ); 45 | static_assert( 46 | util::is_same_v, Type>, 47 | "Wrong callable return type" 48 | ); 49 | static_assert(util::is_const_v, "Callable may have side effects"); 50 | 51 | static_assert( 52 | util::is_same_v< 53 | util::args_tuple_t, 54 | util::make_tuple_t 55 | >, 56 | "Wrong adapter arguments types" 57 | ); 58 | static_assert( 59 | util::is_same_v, Type>, 60 | "Wrong adapter return type" 61 | ); 62 | static_assert(util::is_const_v, "Adapter may have side effects"); 63 | 64 | Callable _callable; 65 | Adapter _adapter; 66 | 67 | template 68 | Type _eval(const std::vector& args, std::index_sequence) const { 69 | if (args.size() != argcount) 70 | throw ArgumentsMismatch{argcount, args.size()}; 71 | return _callable(_adapter(args[indices])...); 72 | } 73 | 74 | template 75 | Type _call(const std::vector& args, std::index_sequence) const { 76 | if (args.size() != argcount) 77 | throw ArgumentsMismatch{argcount, args.size()}; 78 | return _callable(args[indices]...); 79 | } 80 | 81 | public: 82 | template 83 | WrapperModel(CallableType&& callable, AdapterType&& adapter) : 84 | _callable{std::forward(callable)}, 85 | _adapter{std::forward(adapter)} 86 | {} 87 | 88 | std::shared_ptr clone() const override { 89 | return std::make_shared(*this); 90 | } 91 | 92 | std::size_t argc() const noexcept override { return argcount; } 93 | 94 | Type eval(const std::vector& args) const override { 95 | return _eval(args, std::make_index_sequence{}); 96 | } 97 | 98 | Type call(const std::vector& args) const override { 99 | return _call(args, std::make_index_sequence{}); 100 | } 101 | }; 102 | 103 | template 104 | using ModelType = WrapperModel< 105 | std::decay_t, 106 | std::decay_t, 107 | util::argc_v 108 | >; 109 | 110 | std::shared_ptr _wrapper; 111 | 112 | Wrapper(std::shared_ptr&& wrapper) : _wrapper{std::move(wrapper)} {} 113 | 114 | protected: 115 | Type eval(const std::vector& args) const { return _wrapper->eval(args); } 116 | 117 | public: 118 | template 119 | Wrapper(Callable&& callable, Adapter&& adapter) : 120 | _wrapper{std::make_shared>( 121 | std::forward(callable), 122 | std::forward(adapter) 123 | )} 124 | {} 125 | 126 | template< 127 | typename Callable, 128 | typename = std::enable_if_t>, 129 | typename = std::enable_if_t> 130 | > 131 | Wrapper(Callable&& callable) : 132 | Wrapper{ 133 | std::forward(callable), 134 | [](const Source& x) { return static_cast(x); } 135 | } 136 | {} 137 | 138 | template< 139 | typename Model, 140 | typename = std::enable_if_t> 141 | > 142 | Wrapper(Model&& model) : _wrapper{std::make_shared(std::forward(model))} {} 143 | 144 | bool operator==(const Wrapper& other) const noexcept { return _wrapper == other._wrapper; } 145 | 146 | bool operator!=(const Wrapper& other) const noexcept { return !operator==(other); } 147 | 148 | template 149 | Type operator()(Args&&... args) const { 150 | return _wrapper->call(util::to_vector(std::forward(args)...)); 151 | } 152 | 153 | template 154 | Type eval(Args&&... args) const { 155 | return _wrapper->eval(util::to_vector(std::forward(args)...)); 156 | } 157 | 158 | std::size_t argc() const noexcept { return _wrapper->argc(); } 159 | 160 | Wrapper copy() const { return Wrapper{_wrapper->clone()}; } 161 | 162 | bool valid() const noexcept { return static_cast(_wrapper); } 163 | }; 164 | 165 | } 166 | 167 | 168 | namespace std { 169 | 170 | template 171 | struct hash> { 172 | size_t operator()(const calculate::Wrapper& wrapper) const { 173 | return hash>>{}(wrapper._wrapper); 174 | } 175 | }; 176 | 177 | } 178 | 179 | #endif 180 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | ![Calculate](resource/calculate.svg) 2 | 3 | | Version | 2.1.1rc10 | 4 | | ------- | -------- | 5 | 6 | --- 7 | 8 | [![Download](https://api.bintray.com/packages/newlawrence/calculate/Calculate%3Anewlawrence/images/download.svg)](https://bintray.com/newlawrence/calculate/Calculate%3Anewlawrence/_latestVersion) 9 | [![MIT licensed](https://img.shields.io/badge/license-MIT-blue.svg)](https://github.com/newlawrence/Calculate/blob/7f96b434dd77461f17a71f3fe3025c21b73ed0d0/copying) 10 | [![Try online](https://img.shields.io/badge/try-online-blue.svg)](https://wandbox.org/permlink/vVyBsIhckIRuvNP9) 11 | [![Build Status](https://travis-ci.org/newlawrence/Calculate.svg?branch=master)](https://travis-ci.org/newlawrence/Calculate) 12 | [![codecov](https://codecov.io/gh/newlawrence/Calculate/branch/master/graph/badge.svg)](https://codecov.io/gh/newlawrence/Calculate) 13 | 14 | Header-only library written in modern **C++** aiming for flexibility and ease of use. **Calculate** is not only a mathematical expressions parser but an engine built on top of the [Shunting Yard algorithm](https://en.wikipedia.org/wiki/Shunting-yard_algorithm). 15 | 16 | The main objective of the library is to offer a clean and intuitive interface, where the expressions act and feel like regular functions. Another objective is to be completely configurable; from the underlying data type to the tokenizing logic, the library is in fact a custom parser factory. 17 | 18 | ```c++ 19 | auto parser = calculate::Parser{}; 20 | auto sum = parser.parse("x+y"); 21 | 22 | sum(1., 2.); // returns 3. 23 | ``` 24 | 25 | **Calculate** is available as a [conan package](https://bintray.com/newlawrence/calculate/Calculate%3Anewlawrence): 26 | 27 | ```bash 28 | # Append calculate to Conan's repositories list 29 | conan remote add calculate https://api.bintray.com/conan/newlawrence/calculate 30 | ``` 31 | 32 | ### Features 33 | 34 | * Generic. `double` and `std::complex` parsers included by default. 35 | * User defined constants, functions, and prefix, suffix and binary operators. 36 | * Infix and postfix notations supported. 37 | * Regex-based customizable lexers. 38 | * Header-only. 39 | 40 | ### Build and test 41 | 42 | **Calculate** doesn't have any third party dependencies, the library should work with any compiler fully compatible with the **C++14** standard. Currently it has being tested under **gcc (5.2+)**, **clang (3.7+)**, **msvc (19.10+)** and **intel (18.0+)**. 43 | 44 | The examples and tests need [CMake](https://cmake.org/) to be built. [Conan](https://conan.io/) can be used to handle the dependencies: 45 | 46 | ```bash 47 | # Build the example (Boost libraries needed) 48 | conan install example --install-folder build/example 49 | cmake -H. -Bbuild -DCALCULATE_BUILD_EXAMPLES=ON 50 | cmake --build build --target example 51 | 52 | # Build and run the tests (Catch2 library needed) 53 | conan install test --install-folder build/test 54 | cmake -H. -Bbuild -DCALCULATE_BUILD_TESTS=ON 55 | cmake --build build --target make_test # build 56 | cmake --build build --target test # run 57 | ``` 58 | 59 | ### User guide 60 | 61 | Want to try? Check out **Calculate**'s [wiki](https://github.com/newlawrence/Calculate/wiki) to get started. 62 | 63 | **License:** MIT (see `copying`). 64 | -------------------------------------------------------------------------------- /resource/calculate.svg: -------------------------------------------------------------------------------- 1 | 2 | 13 | 15 | 17 | 18 | 20 | image/svg+xml 21 | 23 | 24 | 25 | 26 | 27 | 30 | 33 | 41 | 45 | 50 | 51 | 55 | 56 | 59 | 67 | 71 | 76 | 77 | 81 | 82 | 86 | 128 | 136 | 140 | 145 | 146 | 150 | 151 | = 165 | 173 | 177 | 182 | 183 | 187 | 188 | 191 | 199 | 204 | 206 | 208 | 213 | 214 | 217 | 218 | 219 | 220 | 223 | 231 | 236 | 238 | 240 | 245 | 246 | 249 | 250 | 251 | 252 | 286 | 296 | Math expressions parser 307 | 317 | 327 | Calculate 338 | 339 | 340 | -------------------------------------------------------------------------------- /resource/logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 13 | 15 | 17 | 18 | 20 | image/svg+xml 21 | 23 | 24 | 25 | 26 | 27 | 30 | 33 | 41 | 45 | 50 | 51 | 55 | 56 | 59 | 67 | 71 | 76 | 77 | 81 | 82 | 86 | 128 | 136 | 140 | 145 | 146 | 150 | 151 | = 165 | 173 | 177 | 182 | 183 | 187 | 188 | 191 | 199 | 204 | 206 | 208 | 213 | 214 | 217 | 218 | 219 | 220 | 223 | 231 | 236 | 238 | 240 | 245 | 246 | 249 | 250 | 251 | 252 | 253 | 254 | -------------------------------------------------------------------------------- /test/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.5) 2 | project(Calculate-Tests LANGUAGES CXX) 3 | 4 | 5 | if(CMAKE_SOURCE_DIR STREQUAL PROJECT_SOURCE_DIR) 6 | set(CMAKE_CONFIGURATION_TYPES "Release;Debug" CACHE STRING "Configurations" FORCE) 7 | if(NOT CMAKE_BUILD_TYPE) 8 | set(CMAKE_BUILD_TYPE "Debug" CACHE STRING "Build type" FORCE) 9 | endif() 10 | else() 11 | message(STATUS "Calculate: tests enabled") 12 | endif() 13 | enable_testing() 14 | 15 | find_package(Catch2 QUIET) 16 | if(EXISTS "${CMAKE_BINARY_DIR}/test/conanbuildinfo.cmake") 17 | include("${CMAKE_BINARY_DIR}/test/conanbuildinfo.cmake") 18 | conan_basic_setup(TARGETS NO_OUTPUT_DIRS) 19 | set(CALCULATE_TESTS_USE_CONAN TRUE) 20 | elseif(EXISTS "${CMAKE_BINARY_DIR}/conanbuildinfo.cmake") 21 | include("${CMAKE_BINARY_DIR}/conanbuildinfo.cmake") 22 | conan_basic_setup(TARGETS NO_OUTPUT_DIRS) 23 | set(CALCULATE_TESTS_USE_CONAN TRUE) 24 | elseif(Catch2_FOUND) 25 | set(CALCULATE_TESTS_USE_CONAN FALSE) 26 | else() 27 | message(FATAL_ERROR "Calculate: missing Catch2 library") 28 | endif() 29 | 30 | 31 | 32 | if(DEFINED ENV{CV}) 33 | find_program(GCOV $ENV{CV}) 34 | else() 35 | find_program(GCOV gcov) 36 | endif() 37 | 38 | 39 | set(CALCULATE_BUILD_COVERAGE FALSE) 40 | if( 41 | (CMAKE_CXX_COMPILER_ID MATCHES GNU OR CMAKE_CXX_COMPILER_ID MATCHES Clang) AND 42 | CMAKE_BUILD_TYPE MATCHES Debug AND GCOV 43 | ) 44 | set(CALCULATE_BUILD_COVERAGE TRUE) 45 | set(CALCULATE_COVERAGE_DIR "${CMAKE_BINARY_DIR}/coverage") 46 | file(MAKE_DIRECTORY "${CALCULATE_COVERAGE_DIR}") 47 | file(MAKE_DIRECTORY "${CALCULATE_COVERAGE_DIR}/bin") 48 | file(MAKE_DIRECTORY "${CALCULATE_COVERAGE_DIR}/source") 49 | file(MAKE_DIRECTORY "${CALCULATE_COVERAGE_DIR}/source/temp") 50 | add_custom_target(coverage) 51 | message(STATUS "Calculate: targets configured for coverage using '${GCOV}'") 52 | 53 | add_custom_command(TARGET coverage 54 | POST_BUILD 55 | COMMAND ${CMAKE_COMMAND} -E echo "Generating gcov sources..." 56 | COMMAND ${CMAKE_COMMAND} -E make_directory "${CALCULATE_COVERAGE_DIR}/source/temp" 57 | COMMAND ${CMAKE_COMMAND} -E chdir "${CALCULATE_COVERAGE_DIR}/source/temp" 58 | ${GCOV} "${CMAKE_CURRENT_SOURCE_DIR}/source/*.cpp" 59 | -o "${CALCULATE_COVERAGE_DIR}/bin" -p > /dev/null 60 | COMMAND ${CMAKE_COMMAND} -E copy 61 | "${CALCULATE_COVERAGE_DIR}/source/temp/*Calculate#include*.gcov" 62 | "${CALCULATE_COVERAGE_DIR}/source" 63 | COMMAND ${CMAKE_COMMAND} -E remove_directory "${CALCULATE_COVERAGE_DIR}/source/temp" 64 | ) 65 | 66 | find_program(LCOV lcov) 67 | find_program(GENHTML genhtml) 68 | if(LCOV AND GENHTML) 69 | file(MAKE_DIRECTORY "${CALCULATE_COVERAGE_DIR}/report") 70 | set(coverage_info "${CALCULATE_COVERAGE_DIR}/report/coverage.info") 71 | add_custom_command(TARGET coverage 72 | POST_BUILD 73 | COMMAND ${CMAKE_COMMAND} -E echo 74 | "Generating coverage report..." 75 | COMMAND ${LCOV} -c -d "${CALCULATE_COVERAGE_DIR}/bin" 76 | -o "${coverage_info}" --gcov-tool ${GCOV} > /dev/null 77 | COMMAND ${LCOV} -e "${coverage_info}" "*Calculate/include*" 78 | -o "${coverage_info}" > /dev/null 79 | COMMAND ${GENHTML} "${coverage_info}" 80 | -o "${CALCULATE_COVERAGE_DIR}/report" > /dev/null 81 | ) 82 | message(STATUS "Calculate: enabled coverage report in HTML format") 83 | endif() 84 | endif() 85 | 86 | 87 | file(GLOB TEST_SOURCES RELATIVE "${CMAKE_CURRENT_SOURCE_DIR}" source/*.cpp) 88 | add_custom_target(make_test) 89 | foreach(test_source ${TEST_SOURCES}) 90 | get_filename_component(name "${test_source}" NAME_WE) 91 | set(test_name "test_${name}") 92 | set(test_folder "${CMAKE_BINARY_DIR}/bin") 93 | 94 | add_executable(${test_name} "${test_source}") 95 | set_target_properties(${test_name} 96 | PROPERTIES 97 | CXX_STANDARD_REQUIRED YES 98 | CXX_EXTENSIONS NO 99 | RUNTIME_OUTPUT_DIRECTORY "${test_folder}" 100 | ) 101 | if(CMAKE_SOURCE_DIR STREQUAL PROJECT_SOURCE_DIR) 102 | target_include_directories(${test_name} PRIVATE "${CMAKE_SOURCE_DIR}/../include") 103 | else() 104 | target_include_directories(${test_name} PRIVATE "${CMAKE_SOURCE_DIR}/include") 105 | endif() 106 | 107 | target_compile_features(${test_name} PRIVATE cxx_std_14) 108 | target_compile_options(${test_name} 109 | PRIVATE 110 | $<$: -pedantic -Wall -Wextra -Werror -Wno-noexcept-type> 111 | $<$: -pedantic -Wall -Wextra -Werror -Qunused-arguments> 112 | ) 113 | if(CALCULATE_BUILD_COVERAGE) 114 | set_target_properties(${test_name} PROPERTIES LINK_FLAGS --coverage) 115 | target_compile_options(${test_name} PRIVATE -O0 -fno-inline -fprofile-arcs -ftest-coverage) 116 | endif() 117 | 118 | if(CALCULATE_TESTS_USE_CONAN) 119 | target_link_libraries(${test_name} CONAN_PKG::Catch2) 120 | else() 121 | target_link_libraries(${test_name} Catch2::Catch2) 122 | endif() 123 | add_dependencies(make_test ${test_name}) 124 | add_test(NAME ${test_name} COMMAND "${test_folder}/${test_name}") 125 | 126 | if(CALCULATE_BUILD_COVERAGE) 127 | set(coverage_name "coverage_${name}") 128 | if(CMAKE_SOURCE_DIR STREQUAL PROJECT_SOURCE_DIR) 129 | set(CALCULATE_GCOV_DIR "${CMAKE_BINARY_DIR}/CMakeFiles/${test_name}.dir/source/") 130 | else() 131 | set(CALCULATE_GCOV_DIR "${CMAKE_BINARY_DIR}/test/CMakeFiles/${test_name}.dir/source/") 132 | endif() 133 | 134 | add_custom_target(${coverage_name} 135 | COMMAND ${CMAKE_COMMAND} -E copy 136 | "${CALCULATE_GCOV_DIR}/${name}.cpp.gcda" 137 | "${CALCULATE_COVERAGE_DIR}/bin/${name}.gcda" 138 | COMMAND ${CMAKE_COMMAND} -E copy 139 | "${CALCULATE_GCOV_DIR}/${name}.cpp.gcno" 140 | "${CALCULATE_COVERAGE_DIR}/bin/${name}.gcno" 141 | ) 142 | add_dependencies(coverage ${coverage_name}) 143 | endif() 144 | endforeach() 145 | -------------------------------------------------------------------------------- /test/conanfile.txt: -------------------------------------------------------------------------------- 1 | [requires] 2 | Catch2/2.9.1@catchorg/stable 3 | 4 | [generators] 5 | cmake 6 | -------------------------------------------------------------------------------- /test/source/parser.cpp: -------------------------------------------------------------------------------- 1 | #define CATCH_CONFIG_MAIN 2 | #include 3 | 4 | #include "calculate.hpp" 5 | 6 | 7 | SCENARIO( "Testing default parser", "[default_parser]" ) { 8 | 9 | GIVEN( "provisional example to trigger the coverage of all the files" ) { 10 | auto parser = calculate::Parser{}; 11 | auto sum = parser.parse("x+y"); 12 | CHECK( sum(1., 2.) == Approx(3.) ); 13 | } 14 | 15 | } 16 | -------------------------------------------------------------------------------- /test/source/symbol.cpp: -------------------------------------------------------------------------------- 1 | #define CATCH_CONFIG_MAIN 2 | #include 3 | 4 | #include "calculate/parser.hpp" 5 | 6 | 7 | SCENARIO( "some static assertions on symbols", "[assertions][symbols]" ) { 8 | using Parser = calculate::BaseParser; 9 | using Symbol = calculate::Symbol; 10 | 11 | static_assert(!std::is_default_constructible::value, ""); 12 | static_assert(std::is_nothrow_destructible::value, ""); 13 | static_assert(std::has_virtual_destructor::value, ""); 14 | } 15 | 16 | 17 | SCENARIO( "some static assertions on variables", "[assertions][variables]" ) { 18 | using Parser = calculate::BaseParser; 19 | using Variable = calculate::Variable; 20 | 21 | static_assert(std::is_final::value, ""); 22 | static_assert(!std::is_default_constructible::value, ""); 23 | static_assert(std::is_nothrow_destructible::value, ""); 24 | static_assert(std::is_copy_constructible::value, ""); 25 | static_assert(std::is_nothrow_move_constructible::value, ""); 26 | static_assert(std::is_copy_assignable::value, ""); 27 | static_assert(std::is_move_assignable::value, ""); 28 | static_assert(std::has_virtual_destructor::value, ""); 29 | } 30 | 31 | SCENARIO( "variables usage", "[usage][variables]" ) { 32 | using Parser = calculate::BaseParser; 33 | using Symbol = calculate::Symbol; 34 | using Variable = calculate::Variable; 35 | 36 | GIVEN( "some variables" ) { 37 | int a = 1, b = 2; 38 | Variable v1 = a; 39 | auto v2 = Variable{b}; 40 | 41 | REQUIRE( v1.type() == Symbol::SymbolType::CONSTANT ); 42 | REQUIRE( v2.type() == Symbol::SymbolType::CONSTANT ); 43 | 44 | THEN( "they wrap an external symbol" ) { 45 | CHECK( static_cast(v1)() == 1 ); 46 | a = 2; 47 | CHECK( static_cast(v1)() == 2 ); 48 | } 49 | 50 | THEN( "their hashes are equal to their stored values" ) { 51 | CHECK( std::hash{}(v1) == std::hash{}(1) ); 52 | a = 2; 53 | CHECK( std::hash{}(v1) == std::hash{}(2) ); 54 | } 55 | 56 | THEN( "they are always different between them" ) { 57 | CHECK( v1 != v2 ); 58 | a = 2; 59 | CHECK( v1 != v2 ); 60 | auto v3 = v1; 61 | CHECK( v1 != v3 ); 62 | auto v4 = static_cast(*v1.clone()); 63 | CHECK( v1 != v4 ); 64 | } 65 | } 66 | } 67 | 68 | 69 | SCENARIO( "some static assertions on constants", "[assertions][constants]" ) { 70 | using Parser = calculate::BaseParser; 71 | using Constant = calculate::Constant; 72 | 73 | static_assert(std::is_final::value, ""); 74 | static_assert(!std::is_default_constructible::value, ""); 75 | static_assert(std::is_nothrow_destructible::value, ""); 76 | static_assert(std::is_copy_constructible::value, ""); 77 | static_assert(std::is_nothrow_move_constructible::value, ""); 78 | static_assert(std::is_copy_assignable::value, ""); 79 | static_assert(std::is_move_assignable::value, ""); 80 | static_assert(std::has_virtual_destructor::value, ""); 81 | } 82 | 83 | SCENARIO( "constants usage", "[usage][constants]" ) { 84 | using Parser = calculate::BaseParser; 85 | using Symbol = calculate::Symbol; 86 | using Constant = calculate::Constant; 87 | 88 | GIVEN( "some constants" ) { 89 | Constant c1 = 1; 90 | auto c2 = Constant{2}; 91 | 92 | REQUIRE( c1.type() == Symbol::SymbolType::CONSTANT ); 93 | REQUIRE( c2.type() == Symbol::SymbolType::CONSTANT ); 94 | 95 | THEN( "they can be converted to its wrapped value" ) { 96 | CHECK( static_cast(c1) == 1 ); 97 | CHECK( int{c2} == 2 ); 98 | CHECK( std::hash{}(Constant{3}) == std::hash{}(3) ); 99 | } 100 | 101 | THEN( "they can be reassigned from non-temporary constants" ) { 102 | c1 = c2; 103 | CHECK( c1 == c2 ); 104 | CHECK( static_cast(c1) == static_cast(c2) ); 105 | CHECK( std::hash{}(c1) == std::hash{}(c2) ); 106 | } 107 | 108 | THEN( "they can be reassigned from temporary constants" ) { 109 | c1 = Constant{3}; 110 | CHECK( c1 != c2 ); 111 | CHECK( static_cast(c1) != static_cast(c2) ); 112 | CHECK( std::hash{}(c1) != std::hash{}(c2) ); 113 | } 114 | 115 | THEN( "cloning has the same effect as copying" ) { 116 | c1 = static_cast(*c2.clone()); 117 | CHECK( c1 == c2 ); 118 | CHECK( static_cast(c1) == static_cast(c2) ); 119 | CHECK( std::hash{}(c1) == std::hash{}(c2) ); 120 | } 121 | 122 | THEN( "they can be converted implicitly" ) { 123 | auto check = [](int i) -> bool { return i == 0; }; 124 | CHECK( check(Constant{0}) ); 125 | CHECK( c1 + 1 == 2 ); 126 | CHECK( c1 - 1 == 0 ); 127 | CHECK( c1 * 2 == 2 ); 128 | CHECK( c1 / 2 == 0 ); 129 | CHECK( c1 % 3 == 1 ); 130 | CHECK( c2 == 2 ); 131 | CHECK( c2 != 0 ); 132 | CHECK( c2 > 1 ); 133 | CHECK( c2 < 3 ); 134 | CHECK( c2 >= 0 ); 135 | CHECK( c2 <= 4 ); 136 | } 137 | } 138 | } -------------------------------------------------------------------------------- /test/source/wrapper.cpp: -------------------------------------------------------------------------------- 1 | #define CATCH_CONFIG_MAIN 2 | #include 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | #include "calculate/wrapper.hpp" 9 | 10 | 11 | class CopyMove { 12 | public: 13 | const std::size_t copied = 0; 14 | const std::size_t moved = 0; 15 | 16 | CopyMove() = default; 17 | CopyMove(std::size_t cp, std::size_t mv) noexcept : copied(cp), moved(mv) {} 18 | CopyMove(const CopyMove& other) noexcept : copied{other.copied + 1}, moved{other.moved} {} 19 | CopyMove(CopyMove&& other) noexcept : copied(other.copied), moved(other.moved + 1) {} 20 | ~CopyMove() = default; 21 | 22 | auto operator()() const noexcept { return CopyMove{copied, moved}; } 23 | }; 24 | 25 | class Intermediary { 26 | int _n; 27 | 28 | public: 29 | explicit Intermediary(int n) : _n{n} {} 30 | Intermediary() : Intermediary{0} {} 31 | 32 | explicit operator int() const noexcept { return _n; } 33 | }; 34 | 35 | 36 | SCENARIO( "some static assertions on the Wrapper class", "[assertions]" ) { 37 | using Wrapper = calculate::Wrapper; 38 | 39 | static_assert(!std::is_default_constructible::value, ""); 40 | static_assert(std::is_copy_constructible::value, ""); 41 | static_assert(std::is_nothrow_move_constructible::value, ""); 42 | static_assert(std::is_copy_assignable::value, ""); 43 | static_assert(std::is_move_assignable::value, ""); 44 | static_assert(std::is_nothrow_destructible::value, ""); 45 | static_assert(!std::has_virtual_destructor::value, ""); 46 | } 47 | 48 | SCENARIO( "construction of a wrapper object", "[construction]" ) { 49 | GIVEN( "a copyable and movable callable class" ) { 50 | using Wrapper = calculate::Wrapper; 51 | 52 | WHEN( "a wrapper of a non temporary object is created" ) { 53 | auto callable = CopyMove{}; 54 | auto wrapper = Wrapper{callable}; 55 | 56 | THEN( "the given object is copied and not moved" ) { 57 | CHECK( wrapper().copied == 1 ); 58 | CHECK( wrapper().moved == 0 ); 59 | } 60 | } 61 | 62 | WHEN( "a wrapper of a temporary object is created" ) { 63 | auto wrapper = Wrapper{CopyMove{}}; 64 | 65 | THEN( "the given object is moved and not copied" ) { 66 | CHECK( wrapper().copied == 0 ); 67 | CHECK( wrapper().moved == 1 ); 68 | } 69 | } 70 | } 71 | } 72 | 73 | SCENARIO( "copy wrapper objects", "[copy]" ) { 74 | using Wrapper = calculate::Wrapper; 75 | const auto hash = std::hash{}; 76 | 77 | GIVEN( "a wrapper object" ) { 78 | auto wrapper = Wrapper{[]{ return 0; }}; 79 | 80 | WHEN( "a shallow copy is performed" ) { 81 | auto copy = wrapper; 82 | 83 | THEN( "they share the wrapped callable" ) { 84 | CHECK( wrapper == copy ); 85 | CHECK( !(wrapper != copy) ); 86 | CHECK( hash(wrapper) == hash(copy) ); 87 | } 88 | } 89 | 90 | WHEN( "a deep copy is performed" ) { 91 | auto copy = wrapper.copy(); 92 | 93 | THEN( "they don't share the wrapped callable" ) { 94 | CHECK( !(wrapper == copy) ); 95 | CHECK( wrapper != copy ); 96 | CHECK( hash(wrapper) != hash(copy) ); 97 | } 98 | } 99 | } 100 | } 101 | 102 | SCENARIO( "moved from state wrappers", "[move]" ) { 103 | using Wrapper = calculate::Wrapper; 104 | 105 | GIVEN( "a wrapper object" ) { 106 | auto wrapper = Wrapper{[]{ return 0; }}; 107 | 108 | WHEN( "it is moved" ) { 109 | auto other = std::move(wrapper); 110 | CHECK( !wrapper.valid() ); 111 | 112 | THEN( "it cannot be used again until being reassigned" ) { 113 | wrapper = []{ return 0; }; 114 | CHECK( wrapper.valid() ); 115 | } 116 | } 117 | } 118 | } 119 | 120 | SCENARIO( "calling wrapper objects", "[call]" ) { 121 | using Wrapper = calculate::Wrapper; 122 | 123 | GIVEN( "a wrapped callable" ) { 124 | auto wrapper = Wrapper{[](int a, int b) noexcept { return a + b; }}; 125 | 126 | THEN( "it can be called directly or used on any iterable container" ) { 127 | int carray[] = {1, 2}; 128 | auto array = std::array{1, 2}; 129 | auto list = std::list{1, 2}; 130 | auto vector = std::vector{1, 2}; 131 | 132 | REQUIRE_NOTHROW( wrapper(1, 2) ); 133 | REQUIRE_NOTHROW( wrapper(carray) ); 134 | REQUIRE_NOTHROW( wrapper(array) ); 135 | REQUIRE_NOTHROW( wrapper(list) ); 136 | REQUIRE_NOTHROW( wrapper(vector) ); 137 | CHECK( wrapper(1, 2) == 3 ); 138 | CHECK( wrapper(carray) == 3 ); 139 | CHECK( wrapper(array) == 3 ); 140 | CHECK( wrapper(list) == 3 ); 141 | CHECK( wrapper(vector) == 3 ); 142 | } 143 | 144 | WHEN( "the wrapper object is called with the wrong number of arguments" ) { 145 | THEN( "it throws an exception" ) { 146 | CHECK_THROWS_AS( wrapper(1), calculate::ArgumentsMismatch ); 147 | CHECK_NOTHROW( wrapper(1, 2)); 148 | CHECK_THROWS_AS( wrapper(1, 2, 3), calculate::ArgumentsMismatch ); 149 | } 150 | } 151 | } 152 | } 153 | 154 | SCENARIO( "evaluating wrapper objects", "[evaluation]" ) { 155 | using Wrapper = calculate::Wrapper; 156 | 157 | GIVEN( "a wrapped callable using the default adapter" ) { 158 | auto wrapper = Wrapper{[](int n) noexcept { return n; }}; 159 | 160 | THEN( "it can be evaluated directly or used on any iterable container" ) { 161 | Intermediary carray[] = {Intermediary{1}}; 162 | auto array = std::array{Intermediary{1}}; 163 | auto list = std::list{Intermediary{1}}; 164 | auto vector = std::vector{Intermediary{1}}; 165 | 166 | REQUIRE_NOTHROW( wrapper.eval(Intermediary{1}) ); 167 | REQUIRE_NOTHROW( wrapper.eval(carray) ); 168 | REQUIRE_NOTHROW( wrapper.eval(array) ); 169 | REQUIRE_NOTHROW( wrapper.eval(list) ); 170 | REQUIRE_NOTHROW( wrapper.eval(vector) ); 171 | CHECK( wrapper.eval(Intermediary{1}) == 1 ); 172 | CHECK( wrapper.eval(carray) == 1 ); 173 | CHECK( wrapper.eval(array) == 1 ); 174 | CHECK( wrapper.eval(list) == 1 ); 175 | CHECK( wrapper.eval(vector) == 1 ); 176 | } 177 | 178 | WHEN( "the wrapper object is evaluated with the wrong number of arguments" ) { 179 | THEN( "it throws an exception" ) { 180 | CHECK_THROWS_AS( wrapper.eval(), calculate::ArgumentsMismatch ); 181 | CHECK_NOTHROW( wrapper.eval(Intermediary{1}) ); 182 | } 183 | } 184 | } 185 | 186 | GIVEN ( "a wrapped callable using a custom adapter" ) { 187 | auto wrapper = Wrapper{ 188 | [](int n) noexcept { return n; }, 189 | [](Intermediary i) noexcept { return static_cast(i) + 1; } 190 | }; 191 | 192 | WHEN( "it is called the adapter function is not applied" ) { 193 | CHECK( wrapper(Intermediary{1}) != 2 ); 194 | } 195 | 196 | WHEN( "it is evaluated the adapter function is applied" ) { 197 | CHECK( wrapper.eval(Intermediary{1}) == 2 ); 198 | } 199 | } 200 | } 201 | --------------------------------------------------------------------------------