roman numerals calculator

Bjarne Stroustrup “Programming Principles and Practice Using C++”
Chapter 10 Exercise 7
Using std_lib_facilities.h by Bjarne Stroustrup.

My project includes the following files:
p377_7_romanIntMain.cpp my main cpp file
p377_7_romanIntClass.h my roman integer class header file

p377_7_romanIntMain.cpp my main cpp file

//	Philipp Siedler
//	Bjarne Stroustrup's PPP
//	Chapter 10 Exercise 7

#include "p378_7_romanNumerals_class.h"

const char let = '#';
const char quitProg = 'Q';
const char print = ';';
const char number = '8';
const char name = 'a';
const char constant = 'O';
const char reset = 'R';
const char help = 'H';

struct Token {
	char kind;
	Roman_int value;
	string name;
	Token(char ch) :kind(ch), value(0) { }
	Token(char ch, Roman_int val) :kind(ch), value(val) { }
	Token(char ch, string val) :kind(ch), name(val) { } //Error 1: Line missing
};

class Token_stream {
	bool full;
	Token buffer;
public:
	Token_stream() :full(0), buffer(0) { }
	Token get();
	void unget(Token t) { buffer = t; full = true; }
	void ignore(char);
};

Token Token_stream::get()
{
	if (full) {
		full = false;
		return buffer;
	}

	char ch;
	cin.get(ch);

	while (isspace(ch)) {
		if (ch == '\n') return Token(print); // if newline detected, return print Token
		cin.get(ch);
	}

	switch (ch) {
	case '(':
	case ')':
	case '+':
	case '-':
	case '*':
	case '/':
	case '%':
	case ';':
	case '=':
	case 'k':
	case ',':
	{
		return Token(ch);
	}
	case '#':
	{
		return Token(let);
	}
	case 'N':
	case 'I':
	case 'V':
	case 'X':
	case 'L':
	case 'C':
	case 'D':
	case 'M':
	{
		cin.unget();
		Roman_int val;
		cin >> val;
		return Token(number, val);
	}
	default:
	{
		if (isalpha(ch) || ch == '_') { //is ch a letter?
			string s;
			s += ch;
			while (cin.get(ch) && (isalpha(ch) || isdigit(ch) || ch == '_')) { //reads chars, strings or digits
				s += ch;
			}
			cin.unget(); //puts the most recently read character back into the stream
			if (s == "quit") return Token(quitProg);
			if (s == "const") return Token(constant);
			if (s == "reset") return Token(reset);
			if (s == "help" || s == "Help") return Token(help);
			return Token(name, s);
		}
		error("Bad token");
		return Token(' ');
	}
	}
}

void Token_stream::ignore(char c)
{
	if (full && c == buffer.kind) {
		full = false;
		return;
	}
	full = false;
	char ch;
	while (cin >> ch) {
		if (ch == c) return;
	}
}

struct Variable {
	string name;
	Roman_int value;
	bool immutable;
	Variable(string n, Roman_int v, bool i) :name(n), value(v), immutable(i) { }
};

class Symbol_table {
public:
	vector<Variable> var_table;
	Roman_int get(string);
	void set(string, Roman_int);
	bool is_declared(string);
	Roman_int declare(char);
	bool is_immutable(string);
};

Token_stream ts;
Roman_int expression();
Roman_int primary();

Roman_int Symbol_table::get(string s)
{
	for (int i = 0; i < var_table.size(); ++i) {
		if (var_table[i].name == s) {
			return var_table[i].value;
		}
	}
	error("get: undefined name ", s);
	return 0.0;
}

void Symbol_table::set(string s, Roman_int d)
{
	for (int i = 0; i <= var_table.size(); ++i) {
		if (var_table[i].name == s) {
			var_table[i].value = d;
			return;
		}
	}
	error("set: undefined name ", s);
}

bool Symbol_table::is_declared(string s)
{
	for (int i = 0; i < var_table.size(); ++i) {
		if (var_table[i].name == s) return true;
	}
	return false;
}

Roman_int Symbol_table::declare(char kind)
{
	Token t = ts.get();
	if (t.kind != name) {
		error("name expected in declaration");
	}

	string name = t.name;
	if (kind == let || kind == constant) {
		if (is_declared(name)) error(name, " declared twice");
	}
	else if (kind == reset) {
		if (!is_declared(name))
			error(name, " has not been declared");
		if (is_immutable(name))
			error(name, " is a constant");
	}
	else {
		error("unknown statement");
	}

	Token t2 = ts.get();
	if (t2.kind != '=') error("= missing in declaration of ", name);
	Roman_int d = expression();
	if (is_declared(name))
		set(name, d);
	else
		var_table.push_back(Variable(name, d, (kind == constant)));
	return d;
}

bool Symbol_table::is_immutable(string s)
{
	for (int i = 0; i<int(var_table.size()); ++i)
		if (var_table[i].name == s && var_table[i].immutable) return true;
	return false;
}

Symbol_table st;

Roman_int primary()
{
	Token t = ts.get();
	switch (t.kind) {
	case '(':
	{
		Roman_int d = expression();
		t = ts.get();
		if (t.kind != ')') error("')' expected");
		return d;
	}
	case '-':
	{
		return -primary();
	}
	case '+':
	{
		return primary();
	}
	case number:
	{
		return t.value;
	}
	case name:
	{
		return st.get(t.name);
	}
	default:
	{
		error("primary expected");
	}
	}
}

Roman_int term()
{
	Roman_int left = primary();
	while (true) {
		Token t = ts.get();
		switch (t.kind) {
		case '*':
		{
			left = left * primary();
			break;
		}
		case '/':
		{
			Roman_int d = primary();
			if (d == Roman_int(0)) error("divide by zero");
			left = left / d;
			break;
		}
		case '%':
		{
			Roman_int i1 = left;
			Roman_int i2 = primary();
			if (i2 == Roman_int(0)) error("%: divide by zero");
			left = i1 % i2;
			break;
		}
		default: {
			ts.unget(t);
			return left;
		}
		}
	}
}

Roman_int expression()
{
	Roman_int left = term();
	while (true) {
		Token t = ts.get();
		switch (t.kind) {
		case '+':
		{
			left = left + term();
			break;
		}
		case '-':
		{
			left = left - term();
			break;
		}
		default:
		{
			ts.unget(t);
			return left;
		}
		}
	}
}

Roman_int statement()
{
	Token t = ts.get();
	Roman_int d;
	if (t.kind == let || t.kind == reset || t.kind == constant) {
		d = st.declare(t.kind);
	}
	else {
		ts.unget(t);
		d = expression();
	}
	t = ts.get();
	if (t.kind != print) {
		error("Missing terminator");
	}

	return d;
}

void clean_up_mess()
{
	ts.ignore(print);
}

const string prompt = "> ";
const string result = "= ";

void calculate()
{
	cout << "Welcome to my calculator.\n"
		<< "Just hit enter or ; at the end of your function to calculate.\n"
		<< "Enter help or Help for help or quit to exit the program.\n";
	while (true) try {
		cout << prompt;
		Token t = ts.get();
		while (t.kind == print) {
			t = ts.get();
		}
		if (t.kind == help) {
			cout << "You can use / *-+operators.\n"
				<< "Declaring variables using #, for example: # x = 5.\n";
		}
		else {
			if (t.kind == quitProg) {
				return;
			}
			ts.unget(t);
			cout << result << statement() << endl;
		}
	}
	catch (runtime_error& e) {
		cerr << e.what() << endl;
		clean_up_mess();
	}
}

int main()
try {
	calculate();
	return 0;
}
catch (exception& e) {
	cerr << "exception: " << e.what() << endl;
	char c;
	while (cin >> c&& c != ';');
	return 1;
}
catch (...) {
	cerr << "exception\n";
	char c;
	while (cin >> c && c != ';');
	return 2;
}

p377_7_romanIntClass.h my roman integer class header file

#pragma once
//	Philipp Siedler
//	Bjarne Stroustrup's PPP
//	Chapter 10 Exercise 7

#include "std_lib_facilities.h"

vector<char> roman_chars = {
	'N', 'I', 'V', 'X', 'L', 'C', 'D', 'M'
};

int roman_char_value(char ch) {
	switch (ch) {
	case 'N':
		return 0;
	case 'I':
		return 1;
	case 'V':
		return 5;
	case 'X':
		return 10;
	case 'L':
		return 50;
	case 'C':
		return 100;
	case 'D':
		return 500;
	case 'M':
		return 1000;
	default:
		return -1;
	}
}

class Roman_int {
private:
	string roman_string;
	int roman_int;
public:

	Roman_int() {};
	Roman_int(int _roman_int);
	Roman_int(string _roman_string);

	void set_values();
	void set_values(int);
	string get_string() { return roman_string; }
	int get_int() { return roman_int; }
};

//DEFAULT OVERLOAD------

Roman_int::Roman_int(int _roman_int) {
	roman_int = _roman_int;
}

Roman_int::Roman_int(string _roman_string) {
	roman_string = _roman_string;
}

//DEFAULT OVERLOAD------END


//OPERATOR OVERLOAD-------

ostream& operator<<(ostream& os, Roman_int& _roman_int) {
	os << "Roman_int conversion:\n"
		<< _roman_int.get_string()
		<< " equals " << _roman_int.get_int() << endl;
	return os;
}

Roman_int operator+(Roman_int& r1, Roman_int& r2) {
	Roman_int result(r1.get_int() + r2.get_int());
	return result;
}

Roman_int operator-(Roman_int& r1, Roman_int& r2) {
	Roman_int result(r1.get_int() - r2.get_int());
	return result;
}

Roman_int operator-(Roman_int& r1) {
	Roman_int result(-r1.get_int());
	return result;
}

Roman_int operator*(Roman_int& r1, Roman_int& r2) {
	Roman_int result(r1.get_int() * r2.get_int());
	return result;
}

Roman_int operator/(Roman_int& r1, Roman_int& r2) {
	Roman_int result(r1.get_int() / r2.get_int());
	return result;
}

Roman_int operator%(Roman_int& r1, Roman_int& r2) {
	Roman_int result(r1.get_int() % r2.get_int());
	return result;
}

bool operator==(Roman_int& r1, Roman_int& r2) {
	if (r1.get_int() == r2.get_int()) {
		return true;
	}
	else {
		return false;
	}
}

bool operator!=(Roman_int& r1, Roman_int& r2) {
	if (r1.get_int() != r2.get_int()) {
		return true;
	}
	else {
		return false;
	}
}

bool operator<(Roman_int& r1, Roman_int& r2) {
	if (r1.get_int() < r2.get_int()) {
		return true;
	}
	else {
		return false;
	}
}

bool operator>(Roman_int& r1, Roman_int& r2) {
	if (r1.get_int() > r2.get_int()) {
		return true;
	}
	else {
		return false;
	}
}

bool operator<=(Roman_int& r1, Roman_int& r2) {
	if (r1.get_int() <= r2.get_int()) {
		return true;
	}
	else {
		return false;
	}
}

bool operator>=(Roman_int& r1, Roman_int& r2) {
	if (r1.get_int() >= r2.get_int()) {
		return true;
	}
	else {
		return false;
	}
}

//OPERATOR OVERLOAD-------END


//READ------

bool read_roman_string(string& _value) {
	string _value_temp;
	if (cin >> _value_temp) {
		_value = _value_temp;
		return true;
	}
	else {
		return false;
	}
}

//CHECK VALIDITY------

bool check_string_validity(string& _value) {
	if (_value.size() == 1) {
		cout << "Checking validity of roman char... ";
	}
	else {
		cout << "Checking validity of roman string... ";
	}

	int size = int(_value.size());
	vector<int> invalid_index;

	for (int i = 0; i < size; i++) {
		int check = 0;
		for (int j = 0; j < roman_chars.size(); j++) {
			if (roman_chars[j] == _value[i]) {
				check++;
			}
		}
		if (check == 0) {
			invalid_index.push_back(i);
		}
	}

	if (invalid_index.size() != 0) {
		cout << "Invalid char: ";
		for (int i = 0; i < invalid_index.size(); i++) {
			cout << _value[invalid_index[i]] << " ";
		}
		cout << endl;
		return false;
	}
	else {
		if (_value.size() == 1) {
			cout << "Valid roman char." << endl;
			return true;
		}
		else {
			cout << "Valid roman string." << endl;
			return true;
		}
	}
}

bool check_char_validity(char& _value) {
	cout << "Checking validity of roman char... ";

	int check = 0;
	for (int j = 0; j < roman_chars.size(); j++) {
		if (roman_chars[j] == _value) {
			check++;
		}
	}
	if (check == 0) {
		cout << "Invalid char: " << _value << endl;
		return false;
	}
	else {
		cout << "Valid roman char." << endl;
	}
}

//CHECK COUNT------

bool check_char_count(string& _value) {
	cout << "Checking number of roman chars... ";

	int size = _value.size();
	vector<int> counter(roman_chars.size(), 0);

	for (int i = 0; i < size; i++) {
		for (int j = 0; j < roman_chars.size(); j++) {
			if (_value[i] == roman_chars[j]) {
				counter[j]++;
			}
		}
	}

	int check = 0;
	for (int i = 0; i < roman_chars.size(); i++) {
		if (counter[i] > 1 && (roman_chars[i] == 'V' || roman_chars[i] == 'L' || roman_chars[i] == 'D')) {
			cout << "Invalid number of " << roman_chars[i] << "." << endl;
			check++;
		}
		else if (counter[i] > 3 && (roman_chars[i] == 'I' || roman_chars[i] == 'X' || roman_chars[i] == 'C' || roman_chars[i] == 'M')) {
			cout << "Invalid number of " << roman_chars[i] << "." << endl;
			check++;
		}
	}
	if (check == 0) {
		if (_value.size() == 1) {
			cout << "It is a single char." << endl;
		}
		else {
			cout << "Valid number of chars." << endl;
		}
		return true;
	}
	else {
		return false;
	}
}

//STRING TO INT-------

void decompose_roman_string(string& _value, int& _digit) {
	cout << "Decomposing roman string... ";

	int resulting_val = 0;
	int size = _value.size();
	int check = 0;

	//fill vector
	vector<int> value_ints;
	for (int i = 0; i < size; i++) {
		value_ints.push_back(roman_char_value(_value[i]));
	}

	/*
	I placed before V or X indicates one less, so four is IV (one less than five) and nine is IX (one less than ten)
	X placed before L or C indicates ten less, so forty is XL (ten less than fifty) and ninety is XC (ten less than a hundred)
	C placed before D or M indicates a hundred less, so four hundred is CD (a hundred less than five hundred) and nine hundred is CM (a hundred less than a thousand)
	*/

	if (size == 1) {
		resulting_val = value_ints[0];
	}
	else {
		for (int i = 0; i < size; i++) {
			if (_value[i] == 'I' && (_value[i + 1] == 'V' || _value[i + 1] == 'X')) {
				if (i == 0 && size == 2) {
					resulting_val += (value_ints[i + 1] - value_ints[i]);
					i++;
				}
				else if ((value_ints[i + 1] - value_ints[i]) < resulting_val) {
					resulting_val += (value_ints[i + 1] - value_ints[i]);
					i++;
				}
				else {
					cout << "Number not existing." << endl;
					check = 1;
				}
			}
			else if (_value[i] == 'X' && (_value[i + 1] == 'L' || _value[i + 1] == 'C')) {
				if (i == 0 && size == 2) {
					resulting_val += (value_ints[i + 1] - value_ints[i]);
					i++;
				}
				else if ((value_ints[i + 1] - value_ints[i]) < resulting_val) {
					resulting_val += (value_ints[i + 1] - value_ints[i]);
					i++;
				}
				else {
					cout << "Number not existing." << endl;
					check = 1;
				}
			}
			else if (_value[i] == 'C' && (_value[i + 1] == 'D' || _value[i + 1] == 'M')) {
				if (i == 0 && size == 2) {
					resulting_val += (value_ints[i + 1] - value_ints[i]);
					i++;
				}
				else if ((value_ints[i + 1] - value_ints[i]) < resulting_val) {
					resulting_val += (value_ints[i + 1] - value_ints[i]);
					i++;
				}
				else {
					cout << "Number not existing." << endl;
					check = 1;
				}
			}
			else {
				resulting_val += value_ints[i];
			}
		}
	}

	if (check == 0) {
		cout << "resulting value = " << resulting_val << endl;
		_digit = resulting_val;
	}
}

//SET VALUES------

void Roman_int::set_values() {
	//while (true) {
	cout << "Enter roman string or char:" << endl;
	//read roman string from user
	if (read_roman_string(roman_string)) {
		if (roman_string == "p") {
			//break;
		}
		//check if valid roman chars
		//check if valid count of chars
		else if (check_string_validity(roman_string) && check_char_count(roman_string)) {
			//decompose roman string
			decompose_roman_string(roman_string, roman_int);
			cout << "Roman_int conversion:\n"
				<< get_string()
				<< " equals " << get_int() << endl;
		}
	}
	//}
}

void Roman_int::set_values(int _val) {
	roman_int = _val;
}

istream& operator>>(istream& is, Roman_int& _roman_int) {
	// throw exception if is.bad()
	is.exceptions(is.exceptions() | ios_base::badbit);

	string s;
	char ch;

	// add characters to s until space, newline, eof or invalid character
	while (is.get(ch)) {
		if (check_char_validity(ch)) s += ch;
		else {
			is.putback(ch);
			break;
		}
	}

	// s consists only of valid characters - is it a valid Roman numeral?
	int val;
	decompose_roman_string(s, val);
	_roman_int.set_values(val);
	return is;
}
Output:
Welcome to my calculator.
Just hit enter or ; at the end of your function to calculate.
Enter help or Help for help or quit to exit the program.
> XVI - IV
Checking validity of roman char... Valid roman char.
Checking validity of roman char... Valid roman char.
Checking validity of roman char... Valid roman char.
Checking validity of roman char... Invalid char:
Decomposing roman string... resulting value = 16
Checking validity of roman char... Valid roman char.
Checking validity of roman char... Valid roman char.
Checking validity of roman char... Invalid char:

Decomposing roman string... resulting value = 4
= Roman_int conversion:
 equals 12
>

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google+ photo

You are commenting using your Google+ account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

This site uses Akismet to reduce spam. Learn how your comment data is processed.