Quick actions

cmd+k|ctrl+k

Navigation

Languages

calculator

Snippet info

Language

Cpp

Visibility

public

Author

hyrious

Created

2018-09-11T03:26:21Z

Updated

2018-09-11T03:40:45Z

// https://github.com/kimwalisch/calculator

#include <iostream>
#include <sstream>
#include <string>
#include <vector>
#include <stack>
#include <cstddef>
#include <cctype>
#include <cmath>

struct Calculator {
    // if error occurs, save it here
    bool error;
    std::string message;
    double x, y, t;
    // eval("3 + 5") => 8
    double eval(const std::string& expr, double x, double y, double t) {
        error = false;
        message.clear();
        double value = 0;
        this->x = x;
        this->y = y;
        this->t = t;
        _index = 0;
        _expr = expr;
        value = parseExpr();
        if (!isEnd()) unexpect("EOF");
        return value;
    }
    double eval(const std::string& expr, double x) {
        return eval(expr, x, x, x);
    }
    double eval(const std::string& expr, double x, double y) {
        return eval(expr, x, y, 0);
    }
    //           +    -    *    /    ^    e
    enum { NONE, ADD, SUB, MUL, DIV, POW, EXP };
    //  +   10  left
    struct Operator {
        int op, prec, assoc;
        Operator(int op, int prec, int assoc) :
            op(op), prec(prec), assoc(assoc) {}
    };
    // <+> = 8 in (3 + 5)
    struct Value {
        Operator op;
        double value;
        Value(const Operator& op, double value) :
            op(op), value(value) {}
        int prec() const {
            return op.prec;
        }
        bool none() const {
            return op.op == NONE;
        }
    };
    // for private use
    std::string _expr;
    std::size_t _index;
    std::stack<Value> _stack;
    Calculator() : error(false) {}
    // return error = true if division by 0
    double checkZero(double value) {
        if (value == 0) {
            std::string divmod = "/%";
            std::size_t position = _expr.find_last_of(divmod, _index - 2);
            error = true;
            message = "division by 0";
            if (position != std::string::npos)
                message += " at\"" + _expr.substr(position) + "\"";
        }
        return value;
    }
    // calc atom
    double calculate(double a, double b, const Operator& op) {
        switch (op.op) {
            case ADD:
                return a + b;
            case SUB:
                return a - b;
            case MUL:
                return a * b;
            case DIV:
                return a / checkZero(b);
            case POW:
                return std::pow(a, b);
            case EXP:
                return a * std::pow(10, b);
            default:
                return 0;
        }
    }
    bool isEnd() {
        return _index >= _expr.length();
    }
    char getChar() {
        if (isEnd()) return 0;
        return _expr[_index];
    }
    void expect(const std::string& str) {
        if (_expr.compare(_index, str.length(), str))
            unexpect(str);
        else _index += str.length();
    }
    void unexpect(const std::string& str) {
        error = true;
        message = "unexpected \"" + _expr.substr(_index) + "\" at " +
                  sizeToString(_index) + ", expect \"" + str + "\"";
    }
    std::string sizeToString(std::size_t size) {
        std::stringstream ss;
        ss << size;
        return ss.str();
    }
    void eatSpaces() {
        while (std::isspace(getChar())) _index++;
    }
    Operator parseOp() {
        eatSpaces();
        switch (getChar()) {
            case '+':
                _index++;
                return Operator(ADD, 10, 'L');
            case '-':
                _index++;
                return Operator(SUB, 10, 'L');
            case '/':
                _index++;
                return Operator(DIV, 20, 'L');
            case '*':
                _index++;
                return Operator(MUL, 20, 'L');
            case '^':
                _index++;
                return Operator(POW, 30, 'R');
            case 'e':
            case 'E':
                _index++;
                return Operator(EXP, 40, 'R');
            default:
                return Operator(NONE, 0, 'L');
        }
    }
    static int toInteger(char c) {
        if (c >= '0' && c <= '9') return c - '0';
        if (c >= 'a' && c <= 'f') return c - 'a' + 0xa;
        if (c >= 'A' && c <= 'F') return c - 'A' + 0xa;
        return 0xf + 1; // not an integer
    }
    int getInteger() {
        return toInteger(getChar());
    }
    int parseDec() {
        int value = 0;
        for (int d; (d = getInteger()) <= 9; _index++)
            value = value * 10 + d;
        return value;
    }
    int parseHex() {
        _index += 2;
        int value = 0;
        for (int h; (h = getInteger()) <= 0xf; _index++)
            value = value * 0x10 + h;
        return value;
    }
    bool isHex() {
        if (_index + 2 < _expr.length()) {
            char x = _expr[_index + 1];
            char h = _expr[_index + 2];
            return (x == 'x' || x == 'X') && toInteger(h) <= 0xf;
        }
        return false;
    }
    double parseReal() {
        int dec = 0, base = 10;
        double real = 0;
        if (getChar() != '.') dec = parseDec();
        _index++;
        for (int d; (d = getInteger()) <= 9; _index++) {
            real += (double) d / base;
            base *= 10;
        }
        return dec + real;
    }
    bool isReal() {
        bool result = false;
        int index_ = _index;
        while (std::isdigit(getChar()))
            _index++;
        if (getChar() == '.') {
            _index++;
            result = std::isdigit(getChar());
        } else
            result = false;
        _index = index_;
        return result;
    }
    bool beginWith(const std::string& a, const std::string& b, int start) {
        unsigned n = b.length();
        if (n + start > a.length()) return false;
        for (unsigned i = 0; i < n; ++i)
            if (a[i + start] != b[i]) return false;
        return true;
    }
    bool isSin() {
        return beginWith(_expr, "sin(", _index);
    }
    bool isSqrt() {
        return beginWith(_expr, "sqrt(", _index);
    }
    double parseValue() {
        double value = 0;
        eatSpaces();
        switch (getChar()) {
            case '.':
                value = parseReal();
                break;
            case '0':
                if (isHex())
                    value = parseHex();
                else if (isReal())
                    value = parseReal();
                else
                    value = parseDec();
                break;
            case '1':
            case '2':
            case '3':
            case '4':
            case '5':
            case '6':
            case '7':
            case '8':
            case '9':
                if (isReal())
                    value = parseReal();
                else
                    value = parseDec();
                break;
            case '(':
                _index++;
                value = parseExpr();
                eatSpaces();
                expect(")");
                break;
            case '+':
                _index++;
                value = parseValue();
                break;
            case '-':
                _index++;
                value = -parseValue();
                break;
            case 's':
                if (isSin()) {
                    _index += 4;
                    value = std::sin(parseExpr());
                } else if (isSqrt()) {
                    _index += 5;
                    value = std::sqrt(parseExpr());
                } else
                    unexpect("sin( or sqrt(");
                eatSpaces();
                expect(")");
                break;
            case 'c':
                expect("cos(");
                value = std::cos(parseExpr());
                eatSpaces();
                expect(")");
                break;
            case 'l':
                expect("log(");
                value = std::log(parseExpr());
                eatSpaces();
                expect(")");
                break;
            case 'x':
                _index++;
                value = x;
                break;
            case 'y':
                _index++;
                value = y;
                break;
            case 't':
                _index++;
                value = t;
                break;
            default:
                if (!isEnd()) unexpect("value");
        }
        return value;
    }
    double parseExpr() {
        _stack.push(Value(Operator(NONE, 0, 'L'), 0));
        double value = parseValue();
        while (!_stack.empty()) {
            Operator op(parseOp());
            while (op.prec < _stack.top().prec() ||
                    (op.prec == _stack.top().prec() &&
                     op.assoc == 'L')) {
                if (_stack.top().none()) {
                    _stack.pop();
                    return value;
                }
                value = calculate(_stack.top().value, value, _stack.top().op);
                _stack.pop();
            }
            _stack.push(Value(op, value));
            value = parseValue();
        }
        return 0;
    }
};

// sinx + 8y
// -> 2sin(x) + 8y          sinx -> sin(x)
// -> 2 * sin(1) + 8 * 2    x, y -> 1, 2
std::string& gsub(std::string& x, const std::string& p, const std::string& r) {
    std::string::size_type i = 0;
    while ((i = x.find(p, i)) != std::string::npos) {
        x.replace(i, p.length(), r);
        i += r.length();
    }
    return x;
}

// \d\w -> \d * \w
std::string& rewrite(std::string& x) {
    gsub(x, "sinx", "sin(x)");
    gsub(x, "cosx", "cos(x)");
    gsub(x, "logx", "log(x)");
    gsub(x, "sqrtx", "sqrt(x)");
    gsub(x, "siny", "sin(y)");
    gsub(x, "cosy", "cos(y)");
    gsub(x, "logy", "log(y)");
    gsub(x, "sqrty", "sqrt(y)");
    gsub(x, "sint", "sin(t)");
    gsub(x, "cost", "cos(t)");
    gsub(x, "logt", "log(t)");
    gsub(x, "sqrtt", "sqrt(t)");
    for (unsigned i = 0; i < x.length() - 1; ++i)
        if (std::isdigit(x[i]) && std::isalpha(x[i + 1]))
            x.insert(++i, 1, '*');
    return x;
}

int main() {
    Calculator c;
    std::string input = "sqrt(0.2sinx + 8.2y + 7/t) - 5 / 3";
    // std::cin >> input;
    rewrite(input);
    std::cout << input << std::endl;
    std::cout << c.eval(input, 1.2, 0.3, 5) << std::endl;
    return 0;
}
INFO