static char rcsid[] = "$Id: calcul.C,v 2.1 2026/02/15 09:08:33 RogerSeguin Exp $"; /* * Copyright (c) 2026 Roger Seguin * (https://frogzie.duckdns.org) * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #ifndef MAIN #define MAIN 0 #endif /* As an executable program Build: g++ -DMAIN -Wall -O3 -o calcul calcul.C Usage: calcul [options] math_expression As an object file (.o) either for static or dynamic link, or as a shared object (.so) Build: g++ -Wall -O3 -o calcul.o -c calcul.C g++ -fPIC -Wall -O3 -o calcul.o -c calcul.C g++ -fPIC -shared -Wall -O3 -o libcalcul.so calcul.C Invocation: extern "C" double compute_arithmetic(const char *math_exression); */ #include #include #include #include #include #include #include #include #include #include #include #include #include namespace calcul { void usage_short(std::ostream& stream) { stream << "Operators" << std::endl; stream << "\t+\t-\t*\t/" << std::endl; stream << "\tln\tlog\t^\t!" << std::endl; stream << "\tsin\tcos\ttan" << std::endl; stream << "\tsinh\tcosh\ttanh" << std::endl; stream << "Constants\n\tpi\te" << std::endl; } // usage_short() void usage(char *argv0, std::ostream& stream = std::cerr) { const char *prg = basename(argv0); stream << prg << " - Evaluate arithmetic expression" << std::endl; stream << "Usage\n\t" << prg << " [ options ] \'math expression\'" << std::endl; stream << "\t" << prg << " [ options ]" << std::endl; stream << "If no expression is provided, the program will enter an interactive loop\n" << std::endl; usage_short(stream); stream << "Options\n\t-p, --pretty\n\t\tdisplay a digit separator" << std::endl; stream << "Example\n\t" << prg << " --pretty \'(4/3)*pi*10^3\'" << std::endl; stream << "[" << rcsid + 5 << "]" << std::endl; } // usage() uint64_t factorial(uint64_t n) { uint64_t result; result = 1; for (uint64_t i = 1; i <= n; i++) result *= i; return (result); } // factorial() void substituteTerm(std::string *str, const char *oldstr, const char *newstr) { const size_t oldlen = strlen(oldstr); const size_t newlen = strlen(newstr); const bool reset = (oldlen > newlen); // rescan from the start? size_t pos = 0; while ((pos = str->find(oldstr, pos)) != std::string::npos) { str->replace(pos, oldlen, newstr); pos = (reset ? 0 : pos + newlen); } } // substituteTerm() void trim(std::string *str) /* Remove leading and trailing spaces */ { size_t len = str->length(); char* buffer = new char[len + 1]; strcpy (buffer, str->c_str()); char *pc; for (pc = buffer + len - 1; ((*pc == ' ') || (*pc == '\t')); * (pc--) = 0); for (pc = buffer; ((*pc == ' ') || (*pc == '\t')); pc++); *str = std::string(pc); delete[] buffer; } // trim() void compact(std::string *str) { /* Replace consecutive series of tabs or space with single space */ substituteTerm(str, "\t", " "); substituteTerm(str, " ", " "); trim(str); // Remove leading and trailing spaces } // compact() void process (std::vector &vect, const char *oper, double (*func)(double)) /* Compute generic operations with single operand */ { for (size_t i = 0; i < vect.size(); i++) { if (vect[i] == oper) { if (i == vect.size() - 1) throw (std::invalid_argument("ill-formed expression")); vect[i] = std::to_string(func (std::stod (vect[i + 1]))); vect.erase(vect.begin() + i + 1); } } } // process() void process (std::vector &vect, const char oper) /* Compute the specified operation */ { std::string operateur{oper}; for (size_t i = 0; i < vect.size(); i++) { if (vect[i] == operateur) { if ((i == 0) || (i == vect.size() - 1)) throw (std::invalid_argument("ill-formed expression")); double v1 = std::stod (vect[i - 1]), v2 = std::stod (vect[i + 1]); double r; switch (oper) { case '^': r = std::pow(v1, v2); break; case '*': r = v1 * v2; break; case '/': r = v1 / v2; break; case '+': r = v1 + v2; break; case '-': r = v1 - v2; break; default: throw (std::runtime_error("Unexpected error")); } vect[i - 1] = std::to_string(r); vect.erase(vect.begin() + i + 1); vect.erase(vect.begin() + i); i = 0; } } } // process() std::string evalFlatExpr(std::string expr) /* Evaluate an expression that does not contain any parentheses */ { if (expr.empty()) return ""; /* Convert space-separated compund string to vector of strings */ std::stringstream ss(expr); std::istream_iterator begin(ss); std::istream_iterator end; std::vector vect(begin, end); /* Process factorials */ for (size_t i = 0; i < vect.size(); i++) { if (vect[i] == "!") { if (i == 0) throw (std::invalid_argument("ill-formed expression")); vect[i - 1] = std::to_string(factorial (std::stod (vect[i - 1]))); vect.erase(vect.begin() + i); } } /* Exponentiation */ process (vect, '^'); /* Circular functions */ process(vect, "cos", std::cos); process(vect, "sin", std::sin); process(vect, "tan", std::tan); /* Hyperbolic functions */ process(vect, "cosh", std::cosh); process(vect, "sinh", std::sinh); process(vect, "tanh", std::tanh); /* Natural and base-10 logarithm */ process(vect, "ln", std::log); process(vect, "log", std::log10); /* Basic arithmetic */ process (vect, '*'); process (vect, '/'); process (vect, '+'); process (vect, '-'); /* Return the result */ if (vect.size() != 1) throw (std::invalid_argument("ill-formed expression")); double r = std::stod(vect[0]); if (std::isnan(r) || std::isinf(r)) throw (std::invalid_argument("arithmetic error")); return (vect[0]); } // evalFlatExpr() std::string evalNestedExpr(std::string expr) /* Compute any expression, first evaluating recursively all sub expressions within parentheses */ { size_t pos1, pos2; pos2 = expr.find (')'); if (pos2 == std::string::npos) return evalFlatExpr (expr); pos1 = expr.rfind ('(', pos2); std::string nested = expr.substr(pos1 + 1, pos2 - pos1 - 1); return evalNestedExpr( expr.replace(pos1, pos2 - pos1 + 1, evalNestedExpr(nested))); } // evalNestedExpr() double calcul(std::string expr) { substituteTerm(&expr, "[", "("); substituteTerm(&expr, "]", ")"); substituteTerm(&expr, "(", " ("); substituteTerm(&expr, ")", ") "); substituteTerm(&expr, "( ", "("); substituteTerm(&expr, " )", ")"); substituteTerm(&expr, "!", " !"); substituteTerm(&expr, "^", " ^ "); substituteTerm(&expr, "x", "*"); substituteTerm(&expr, "*", " * "); substituteTerm(&expr, "/", " / "); substituteTerm(&expr, "+", " + "); substituteTerm(&expr, "-", " -"); // "-n" is a number; "- n" indicates a subtraction substituteTerm(&expr, "log10", "log"); substituteTerm(&expr, "pi", "3.1415926535897932384626433832795028841971"); substituteTerm(&expr, "e", "2.7182818284590452353602874713526624977572"); substituteTerm(&expr, ",", ""); compact(&expr); double result = 0; try { result = std::stod(evalNestedExpr (expr)); } catch(const std::invalid_argument & e) { std::cerr << "Error - Invalid argument: " << e.what() << std::endl; return std::numeric_limits::quiet_NaN(); } catch(const std::exception & e) { std::cerr << e.what() << std::endl; return std::numeric_limits::quiet_NaN(); } catch(...) { std::cerr << "Unknown error" << std::endl; return std::numeric_limits::quiet_NaN(); } if (result == -0.0) result = 0.0; return result; } // calcul() } // namespace calcul #if (!MAIN) /* Produce a compilation object (either .o or .so) */ extern "C" double compute_arithmetic (const char *expr) /* Entry point function */ { double r = calcul::calcul(std::string(expr)); if (std::isnan(r)) std::cerr << __func__ << "(): evaluation error" << std::endl; return (r); } #else /* Build the executable program */ int main(int argc, char**argv) { bool useDigitSeparator = false; std::string expr = ""; for (int i = 1; i < argc; i++) { /* Concatenate command line parameters into single string */ std::string str = argv[i]; if ((str == "-h") || (str == "--help")) { calcul::usage(argv[0]); return 1; } if ((str == "-p") || (str == "--pretty")) { useDigitSeparator = true; continue; } expr += std::string(" ") + str; } if (useDigitSeparator) std::cout.imbue(std::locale("")); calcul::trim(&expr); double result; if (expr.length() > 0) { /* Process the mathematical expression, display the result and exit */ result = calcul::calcul(expr); if (std::isnan(result)) return 255; std::cout << std::setprecision(13) << result << std::endl; return 0; } /* No expression indicated on command line => interactive mode */ std::cout << "Type \"?\" for help, \"q\" to quit" << std::endl; double prevResult = 0; while (true) { std::cout << "? "; std::getline(std::cin, expr); if ((expr == "q") || (expr == "quit") || (expr == "exit")) break; if (expr == "?") { calcul::usage_short(std::cout); std::cout << "Type \"q\" to quit" << std::endl; continue; } /* Check whether the expression begins with an operator; if so, continue the operation previously started */ std::stringstream ss(expr); std::string first; // 1st part until space ss >> first; if (first == "") // No input continue; switch (first[0]) { case '-': /* "-n" is a number while "- n" indicates a subtraction operation */ if (first.length() > 1) /* This is a negative number, not the operator "-"; so, start a new operation instead of combining the previous result */ break; case '+': case '*': case 'x': case '/': case '^': case '!': /* Continue the previous operation */ expr = std::to_string(prevResult) + expr; break; } result = calcul::calcul(expr); if (!std::isnan(result)) { std::cout << std::setprecision(13) << "= " << result << std::endl; prevResult = result; } } return 0; } // main() #endif