<repo_name>iibrahimli/se_polish_calc<file_sep>/src_polish_calc/src/CMakeLists.txt
project(polish_calc)

set(polish_calc_source main.cpp)
add_executable(${PROJECT_NAME} ${polish_calc_source})<file_sep>/src_polish_calc/include/element.hpp
#ifndef ELEMENT_HPP
#define ELEMENT_HPP

#include <iostream>
#include <vector>
#include <algorithm>
#include <unordered_map>
#include <stack>
#include <cmath>


namespace pc{
    union element;

    size_t              num_operands(char op);
    bool                eval_op(char op, std::stack<element>& opst);
    std::vector<double> _get_operands_from_stack(std::stack<element>& st, size_t n_ops);

     // a map from operations to number of operands
    std::unordered_map<char, size_t> _n_operands({
        {'+', 2},
        {'-', 2},
        {'*', 2},
        {'/', 2},
        {'^', 2},
        {'r', 1}
    });
};


// an operator code or a number
union pc::element{

    unsigned long  _op;
    double         _num;


    // initialize with an operation
    element(char op){
        _num = std::numeric_limits<double>::quiet_NaN();
        ((char*)(&_op))[0] = op;
    }

    // initialize with a number
    element(double num){
        _num = num;
    }

    // check if union contains an operation
    bool is_op() const{
        return std::isnan(_num);
    }

    // returns the char of operation
    char get_op() const{
        if(!is_op()) std::runtime_error("Tried to get operation while the element holds a nummber");
        return ((char*)(&_op))[0];
    }

};


// returns number of needed operands for the operator (ex: 2 if op = '+')
size_t pc::num_operands(char op){
    auto n = _n_operands.find(op);
    if( n != _n_operands.end())
        return n->second;
    else{
        std::runtime_error("Unsupported operation character: " + std::to_string(op));
        return 0;
    }
}


// gets N_OPS elements from the top of ST (from oldest to newest)
std::vector<double> pc::_get_operands_from_stack(std::stack<element>& st, size_t n_ops){
    std::vector<double> ops;

    // check whether there are enough numbers in the stack
    if(st.size() < n_ops){
        std::runtime_error("Stack contains less numbers than needed");
        return ops;
    }

    for(size_t i=0; i<n_ops; ++i){
        ops.push_back(st.top()._num);
        st.pop();
    }

    // reverse the order so that the oldest element is first in the vector
    std::reverse(ops.begin(), ops.end());

    return ops;
}


// evaluates the operation modifying the stack as necessary (popping operands
// and pushing the result) and returns boolean indicating success
bool  pc::eval_op(char op, std::stack<element>& opst){
    auto oper = _n_operands.find(op);
    if( oper != _n_operands.end()){
        // op is a valid operation
        
        double res = 0;
        auto ops = _get_operands_from_stack(opst, num_operands(op));

        switch(oper->first){
            case '+':
            // addition
                res = ops[0] + ops[1];
                break;

            case '-':
            // subtraction
                res = ops[0] - ops[1];
                break;

            case '*':
            // multiplication
                res = ops[0] * ops[1];
                break;

            case '/':
            // division
                res = ops[0] / ops[1];
                break;

            case '^':
            // power
                res = std::pow(ops[0], ops[1]);
                break;

            case 'r':
            // sqrt
                res = std::sqrt(ops[0]);
                break;
        }

        opst.push(element(res));

        return true;
    }
    else{
        std::runtime_error("Unsupported operation character: " + std::to_string(op));
        return false;
    }
}


std::ostream& operator<<(std::ostream& os, const pc::element& el){
    if(el.is_op()) os << el.get_op();
    else os << el._num;
    
    return os;
}


#endif<file_sep>/src_polish_calc/src/main.cpp
#include <iostream>
#include <string>
#include "pc.hpp"

using namespace std;
using namespace pc;

int main(int argc, const char *argv[]){

    if(argc != 2){
        cout << "No single expression provided" << endl; 
        return 1;
    }

    polish_calc calc;
    string expr = argv[1];

    cout << calc.eval_string(expr) << endl;

    return 0;
}<file_sep>/src_polish_calc/include/pc.hpp
#ifndef PC_HPP
#define PC_HPP

#include <iostream>
#include <sstream>
#include <stack>
#include <cmath>
#include "element.hpp"


namespace pc{
    class polish_calc;
};


class pc::polish_calc{

public:

    // adds an element to the stack and executes if element contains an operation
    void add_element(element el){
        if(el.is_op()){
            // element represents an operation
            
            // evaluates an operator on the given stack (modifies the stack)
            // the reason for this implementation is that the number of operands to an operator
            // is not fixed, and it is even possible to add support for operators accepting more
            // than 2 operands in future
            if(!eval_op(el.get_op(), _opstack))
                std::runtime_error("Error evaluating operation '" + std::to_string(el.get_op()) + "'");
            
        }
        else{
            // element is a number
            _opstack.push(el);
        }
    }


    // just for convenience
    void add_op(char op){
        add_element(element(op));
    }


    // just for convenience
    void add_num(double num){
        add_element(element(num));
    }


    // evaluates a given expression string (MUST be space-separated) (ex: '2 4 + 5 *' will produce 30)
    double eval_string(const std::string& expr){
        std::istringstream in(expr);
        double tmp_num;
        char tmp_op;

        while(in){
            // ignore spaces
            if(std::isspace(in.peek()) || in.peek() == '\0'){
                in.ignore(1);
                continue;
            }
            else{
                // a digit means that we need to read a number
                if(std::isdigit(in.peek())){
                    if(!(in >> tmp_num)) break;
                    add_element(element(tmp_num));
                }
                // read in a character
                else{
                    if(!(in >> tmp_op)) break;
                    add_element(element(tmp_op));
                }
            }
        }

        // expression done, but there are extra numbers left on the stack
        if(_opstack.size() > 1)
            std::cout << "\nStack has " << _opstack.size() << " numbers left. Check the expression maybe" << std::endl;

        return _opstack.top()._num;
    }


    // result of operation (in "manual mode", i.e used with add_*())
    double get_result(){
        if(_opstack.size() != 1) return std::numeric_limits<double>::quiet_NaN();

        return _opstack.top()._num;
    }


    // returns number of elements on the stack
    size_t num_elements(){
        return _opstack.size();
    }


    // clears the contents of the stack
    void clear_stack(){
        while(!_opstack.empty()) _opstack.pop();
    }


private:
    std::stack<element> _opstack;      // operation stack

};


#endif<file_sep>/README.md
[![Build Status](https://travis-ci.com/iibrahimli/se_polish_calc.svg?branch=master)](https://travis-ci.com/iibrahimli/se_polish_calc)

## SE Project: Reverse Polish Notation Calculator

This is a small PW project for my Software Engineering course to get used to
* CMake
* CppUnit
* Git
* Travis CI
<file_sep>/src_polish_calc/test/expr_test.hpp
#include <cppunit/TestCase.h>
#include <cppunit/TestSuite.h>
#include <cppunit/TestCaller.h>
#include "pc.hpp"


class expr_test : public CppUnit::TestFixture {
private:
    pc::polish_calc calc;
    int N_NUMS = 10;

public:

    void setUp() {}

    void tearDown() {}

    void test_expr(){
        std::string expr = "5 4 + 6 - 2 ^ 2 ^ r 4 *";  // -> 36
        CPPUNIT_ASSERT_DOUBLES_EQUAL(calc.eval_string(expr), 36.0, 0.0001);
        calc.clear_stack();

        // big string test
        expr = "";
        for(int i=0; i<N_NUMS; ++i) expr += (std::string(" ") + std::to_string(1.0));
        for(int i=0; i<N_NUMS-1; ++i) expr += (std::string(" ") + "+");
        CPPUNIT_ASSERT_DOUBLES_EQUAL(N_NUMS, calc.eval_string(expr), 0.0001);
    }

    static CppUnit::Test *suite(){
        CppUnit::TestSuite *ts = new CppUnit::TestSuite("String expression evaluation test");
        ts->addTest(new CppUnit::TestCaller<expr_test>("test_expr", &expr_test::test_expr));
        return ts;
    }

};<file_sep>/src_polish_calc/test/main.cpp
#include <cppunit/ui/text/TestRunner.h>
#include "api_test.hpp"
#include "expr_test.hpp"

using namespace std;

int main(){
    CppUnit::TextUi::TestRunner runner;
    runner.addTest(api_test::suite());
    runner.addTest(expr_test::suite());
    runner.run();
}<file_sep>/CMakeLists.txt
cmake_minimum_required(VERSION 2.8)
project(polish_calc LANGUAGES CXX)

include_directories("src_polish_calc/include/")

set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -std=c++11 -Wextra -g")
set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} -Wextra -std=c++11 -O3")

SET (CMAKE_LIBRARY_OUTPUT_DIRECTORY
        ${PROJECT_BINARY_DIR}/bin
        CACHE PATH
        "Single Directory for all"
    )

SET (CMAKE_RUNTIME_OUTPUT_DIRECTORY
        ${PROJECT_BINARY_DIR}/bin
        CACHE PATH
        "Single Directory for all"
    )

SET (CMAKE_ARCHIVE_OUTPUT_DIRECTORY
        ${PROJECT_BINARY_DIR}/lib
        CACHE PATH
        "Single Directory for all"
    )

enable_testing()

add_subdirectory(src_polish_calc/src)
add_subdirectory(src_polish_calc/test)<file_sep>/src_polish_calc/test/CMakeLists.txt
project(polish_calc_test)

add_executable(tests main.cpp)
target_link_libraries(tests cppunit)

add_test(NAME "cppunit" COMMAND "tests")<file_sep>/src_polish_calc/test/api_test.hpp
#include <cppunit/TestCase.h>
#include <cppunit/TestSuite.h>
#include <cppunit/TestCaller.h>
#include "pc.hpp"


class api_test : public CppUnit::TestFixture {
private:
    pc::polish_calc calc;
    int N_NUMS = 500000;

public:

    void setUp() {}

    void tearDown() {}

    void test_api(){
        CPPUNIT_ASSERT(calc.num_elements() == 0);
        calc.add_num(5.556);
        calc.add_num(5.556);
        CPPUNIT_ASSERT(calc.num_elements() == 2);
        calc.clear_stack();
        CPPUNIT_ASSERT(calc.num_elements() == 0);
        
        // large volume addition test
        for(int i=0; i<N_NUMS; ++i) calc.add_num(1);
        for(int i=0; i<N_NUMS-1; ++i) calc.add_op('+');
        CPPUNIT_ASSERT(calc.num_elements() == 1);
        CPPUNIT_ASSERT(calc.get_result() == (double) N_NUMS);
    }

    static CppUnit::Test *suite(){
        CppUnit::TestSuite *ts = new CppUnit::TestSuite("add_*() API test");
        ts->addTest(new CppUnit::TestCaller<api_test>("test_api", &api_test::test_api));
        return ts;
    }

};

