Prosty kalkulator w konsoli tylko dla danych typu int

1

Witam. Prosty kalkulator w konsoli tylko dla danych typu int. Licze na krytyke. Przede wszystkim czy ten kod dobrze sie czyta dla innej osoby ? Kompilacja:

g++ -Wall -std=c++14 -o int_calculator int_calculator.cpp

Lubie programowanie w Linuxie i moim celem jest dolaczenie do projektu(projektow) open source kiedy juz (moze za pare lat) w miare plynnie bede programowac w roznych jezykach, znac Linuxa oraz moze pozniej siec internetowa.

Aktualnie ucze sie C++, Java, C, bash, mam w planach tez PHP, HTML, Javascript, Pythona, ale najpierw wole ogarnac Linuxa, algorytmy i struktury danych, bazy danych a potem web.

Nie jestem mlody, mam juz 35 lat i nie pracuje w IT.

#include <iostream>
#include <vector>
#include <cmath>
#include <climits>

using namespace std;

// Errors
//--------------------------------------------
inline void error(const string& errormessage) {
   throw runtime_error("!!!!! Error: " + errormessage);
}

inline void error(const string& s, const string& s2) {
	error(s + s2);
}

inline void error(const string& s, const char c) {
	cerr << "!!!!! Error: " << s << "'" << c << "'" << endl;
   throw runtime_error("");
}

inline void error(const string& s, int i) {
	cerr << "!!!!! Error: " << s << i <<  endl;
   throw runtime_error("");
}

bool is_in_long_range(double val) {
	if (val >= LONG_MIN && val <= LONG_MAX)
		return true;
	else
		return false;
}

bool is_integer(double x) {
	int integer = x;
	if (integer != x) {
		if (is_in_long_range(x) && 0 == fmod(x, 1))
			cerr << "Parameter long x = " << (long)x << endl;
		else
			cerr << "Parameter double x = " << x << endl;
		cerr << "Int of x = " << integer << endl;
		return false;
		//error("Parameter is NOT int type");
	}
	return true;
}

void check_integer_range(double val) {
	if (val > INT_MAX)
		error("Max number for int is ", INT_MAX);
	if (val < INT_MIN)
		error("Min number for int is ", INT_MIN);
}

// Token char kinds
//--------------------------------------------
constexpr char declaration_key = '#';
constexpr char quit = 'q';
constexpr char print = ';';   
constexpr char number = '8';
constexpr char NAME = 'a';
constexpr char HELP = 'h';
constexpr char SQRT = 'S';
constexpr char POWER = '^';
constexpr char SPACE = 's';
constexpr char NEW_LINE = '\n';
constexpr char CONST = 'C';
constexpr char VARS_VIEW = 'v';

// Token program keywords
//--------------------------------------------
const string quit_name = "quit";
const string POWER_NAME = "pow";
const string CONST_NAME = "const";
const string VARS_VIEW_NAME = "vars_view";
const string SQRT_NAME = "sqrt";
const string HELP_NAME = "help";

// Token program keywords can not be used as variable name
const vector<char> NOT_VARIABLES_TOKENS = { quit, SQRT, HELP, print, POWER, declaration_key,
														CONST, VARS_VIEW };
														
bool equals_case_insensitive(string s1, string s2) {
	const unsigned int SIZE = s1.size();
	if (SIZE != s2.size())
		return false;
		
	for (unsigned int i = 0; i < SIZE; i++) {
		char c1 = tolower(s1[i]);
		char c2 = tolower(s2[i]);
		if (c1 != c2)
			return false;
	}
	
	return true;
}

bool exist(char c, const vector<char>& vec) {
	for (char a : vec) 
		if (a == c) 
			return true;		
	return false;
}

const vector<string> NOT_VARIABLES_NAMES = { quit_name, SQRT_NAME, CONST_NAME, VARS_VIEW_NAME,
											HELP_NAME, POWER_NAME };

bool exist(string s, const vector<string>& vec) {
	for (string a : vec) 
		if (a == s) 
			return true;		
	return false;
}

// Token struct
//--------------------------------------------
struct Token {    // Token has only non-negative numbers, because every token of '-'(minus) char is get separately from number
	char kind;     
	unsigned int value;    // absolute value of labs(INT_MIN) == labs(INT_MAX + 1)
	string name;
	
	Token(char ch) :kind(ch), value(0) { }
	Token(char ch, double val);
	//Token(char ch, double val) :kind(ch), value(val) { }
	Token(char ch, string s) :kind(ch), name(s) { }
};

// Token has only non-negative numbers, because every token of '-'(minus) char is get separately from number
Token::Token(char ch, double val) { 
	if (val != -(long)(INT_MIN) && false == is_integer(val))  // absolute value of labs(INT_MIN) == labs(INT_MAX + 1)
		error("Precondition: Token value must be int type");
	kind = ch;
	value = val;
}

string get_name(const Token& t) {
	char kind = t.kind;
	switch (kind) {
		case quit:
			return quit_name;
		case number:
			return "number";
		case NAME:
			return t.name;
		case SQRT:
			return SQRT_NAME;
		case HELP:
			return HELP_NAME;
		case print:
			return string(1, print);
		case POWER:
			return POWER_NAME;
		case declaration_key:
			return string(1, declaration_key);
		case CONST:
			return CONST_NAME;
		case VARS_VIEW:
			return VARS_VIEW_NAME;
		default:
			return "";
	}
}

struct Manual {
	string manual_string = string("MANUAL for simple calculator. \n\
Using only int type numbers and variables for calculation.\n\
Max number for int is ") + to_string(INT_MAX) +
string(". Min number for int is ") + to_string(INT_MIN) +
"\nIn below informations 'x', 'y', 'z' are treated as number or variable \n\
Signed numbers or variables -x (-x) +x (+x) are allowed \
but --x x++ -+x are unacceptable\n\
To their accept necessary is separation by brackets -(-x) +(-y) \
but every sign must be separated by number or bracket \n\
Operators can not follow each other - between operators must be bracket or variable or number:\n\
\+x*y-z*(-x/t) is OK, but x/-z is unacceptable \n\
Supported set of brackets: \n\
{} [] {[]} () [()] {()} {[()]} \n\
Each kind of brackets may be inside the same kind of bracket: (()) [[]] {{{}}} \n\
{} can not be be inside () and []  \n\
[] can not be be inside ()  \n\
Supported operations: \n\
1. addition x+y \n\
2. subtraction x-y \n\
3. multiplication x*y \n\
4. division x/y \n\
5. modulo division x%y \n\
6. square root sqrt(x) - number of sqrt must be in any supported bracket kind \n\
7. power(exponentiation) " + POWER_NAME + "(x, y) (x to power of y - y is exponent) \n\
number of exponent must be in any supported bracket kind \n\
8. factorial x! \n\
To separate many calculations press " + string(1, print) + " or whitespace or new line \n\
To do operation in many lines enter operator directly before press new line for example 2+3+ENTER: \n\
In new line operation will be continue \n\
To define new variable enter " + string(1, declaration_key) + " whitespace, variable name = value(number or other variable)\n\
Variable name must started with letter or _ and may contain letters, digits and _ \n\
Can not declare identical variable name \n\
To define constant 'variable' enter: " + string(1, declaration_key) + " " + CONST_NAME + " name_of_variable \n\
To display already defined variables enter " + VARS_VIEW_NAME + "\n\
To quit enter " + quit_name + "\n\
To display this manual enter " + HELP_NAME + " case insensitive \n";
};

Manual manual;

char get_char() {
	char ch;
	cin.get(ch);
	if(!cin) {
		cout << "Exit \n";
		exit(0);
	}
	return ch;
}

// Token_stream class
//--------------------------------------------
class Token_stream {
private:
	bool full;
	Token buffer;
	
	Token get_number();
	Token get_single_char_token(char first);
	bool  is_help_token(string s);
	Token get_alphanum_token(char first);
	Token get_other_token(char c);
public:
	Token_stream() :full(false), buffer(0) { }
	
	Token get();
	void unget(Token& t);
	void ignore(char, char);
	Token get_token_after_SPACE();
	void check_name(Token& t);
	bool is_constant_token(Token& t);
	string get_name_string(char first);
	string get_name_string();
};

string Token_stream::get_name_string() {
	string s = "";
	char c;
	while (cin.get(c) && ('_' == c || isalpha(c) || isdigit(c)))
		s += c;
			
	cin.unget();   // to save non-alphanumerical character in input stream
	return s;
}

string Token_stream::get_name_string(char first) {
	string s = "";
	if (isalpha(first) || '_' == first)   // string with only letter at start
		s = first + get_name_string();     // get alphanumerical string
	return s;
}

void Token_stream::unget(Token& t) {
	if (full) 
		error("unget() into a full buffer");
	buffer = t;       // copy t to buffer
	full = true;      // buffer is now full
}

// function get only non-negative numbers, because every token of '-' char is get separately from number
Token Token_stream::get_number() {	 
	double val;
	cin >> val;
	if (!cin)
		error("Attempt to reading number has failed");
	if (val != -(long)(INT_MIN) && false == is_integer(val))  // absolute value of labs(INT_MIN) == labs(INT_MAX + 1)
		error("input value is not int type ");
	
	return Token(number, val);
}

Token EMPTY_TOKEN = Token(0);  // to signal that get of token is unsuccessfull

Token Token_stream::get_single_char_token(char first) {
	if (isspace(first)) 
		return Token(SPACE);
		
	if (first != declaration_key)
		return EMPTY_TOKEN; // not single token
		
	Token result = EMPTY_TOKEN;
	char next = get_char();   // get next char after 'first' char
	cin.unget();   // to save 'next' character in input stream
	if (first == declaration_key) {
		if (next != '\n' && isspace(next))
			result = Token(declaration_key);
		else
			error("After declaration key must be whitespace except new line");
	} 
	return result;
}

bool Token_stream::is_help_token(string s) {
	if (false == equals_case_insensitive(s, HELP_NAME))
		return false;
	
	char next = get_char();   // get next char after string s
	cin.unget();   // to save 'next' character in input stream	
	if (next == print || isspace(next))
		return true;
	else if (! isalnum(next))
		error("After help name must be whitespace or " + string(1, print) + 
						" or alphanumerical key or " + declaration_key);
}

Token Token_stream::get_alphanum_token(char first) {
	string s = get_name_string(first);
	if (s == quit_name) 
		return Token(quit);
	else if (s == POWER_NAME) 
		return Token(POWER);
	else if (s == SQRT_NAME) 
		return Token(SQRT);
	else if (s == CONST_NAME) 
		return Token(CONST);
	else if (s == VARS_VIEW_NAME) 
		return Token(VARS_VIEW);
	else if (is_help_token(s))
		return Token(HELP);
	else if (s.size() > 0) 
		return Token(NAME, s);
		
	return EMPTY_TOKEN;
}

void Token_stream::check_name(Token& t) {
	if (exist(t.kind, NOT_VARIABLES_TOKENS)) {
		string keyword_name = get_name(t);
		error(keyword_name + " is keyword and can not be used as variable");
	}
	if (exist(t.name, NOT_VARIABLES_NAMES)) 
		error(t.name + " is keyword and can not be used as variable");
	if (t.kind != NAME) { 
		unget(t);
		error("variable name expected in declaration",
			" name must started with letter or _ and may contain letters, digits and _ ");
	}
}

bool Token_stream::is_constant_token(Token& t) {
	bool result = false;
	if (t.kind == CONST) { 
		result = true;
		t = get_token_after_SPACE();  // if constant token then get next token (should be variable name)
		if (t.kind == '=' || t.kind == NEW_LINE) {
			unget(t);
			error(CONST_NAME + " is keyword and can not be used as variable. \n",
				"After this keyword must be one new declared name, = as assignment operator, value");
		}
	}
	return result;
}

Token Token_stream::get_other_token(char first) {
	Token result = get_single_char_token(first);
	if (result.kind == EMPTY_TOKEN.kind)	
		result = get_alphanum_token(first);
	if (result.kind != EMPTY_TOKEN.kind)	
		return result;
	else
		error("Bad token: ", first);
}

Token Token_stream::get_token_after_SPACE() {
	Token t = get();
	while (t.kind == SPACE)
		t = get();
	if (t.kind == HELP)
		throw manual; 

	return t;
}

Token Token_stream::get() {
	if (full) { 
		full = false; 
		return buffer; 
	}
	char ch = get_char();
	switch (ch) {
		case NEW_LINE:
		case '(': case ')': 
		case '[': case ']': 
		case '{': case '}': 
		case '+': case '-': 
		case '*': case '/': case '%':
		case '=':  
		case print:
		case '!':
		case ',':
			return Token(ch);
		case '0': case '1': case '2': case '3': case '4':
		case '5': case '6': case '7': case '8': case '9':
			cin.unget();
			return get_number();
		default:
			return get_other_token(ch);
	}
}

void Token_stream::ignore(char c1, char c2) {
	if (full && (c1 == buffer.kind || c2 == buffer.kind)) {
		full = false;
		return;
	}
	full = false;

	char ch;
	do {
		cin.get(ch);
	} while (cin && ch != c1 && ch != c2);

	return;
}

// global variable of type Token_stream 
//--------------------------------------------
Token_stream ts;

// Variable struct
struct Variable {
	string name;
	int value;
	bool constant;
	Variable(string n, int v) : name(n), value(v), constant(false) { }
	Variable(string n, int v, bool c) : name(n), value(v), constant(c) { }
};

// Symbol_table class has Variable struct vector and operations on this vector
//--------------------------------------------
class Symbol_table {
	private:
		vector<Variable> names;
	public:
		int get_value(string s);
		void set_value(string s, double d);
		bool is_declared(string s);
		int define_name(string s, double value, bool is_constant);
		void print_variables();
};

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

void Symbol_table::set_value(string s, double v) {
	if (! is_integer(v))
		error("set precondition: input parameter is not type int ");
	for (unsigned int i = 0; i < names.size(); ++i)
		if (names[i].name == s) {
			if (names[i].constant == true)
				error("set: constant name ", s);
			names[i].value = v;
			return;
		}
	error("set: undefined name ", s);
}

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

int Symbol_table::define_name(string s, double value, bool is_constant) {
	if (! is_integer(value))
		error("set precondition: input parameter is not type int ");
	if (is_declared(s)) 
		error(s, " declared twice");
	names.push_back(Variable(s, value, is_constant));
	return value;
}

void Symbol_table::print_variables() {
	cout << "\n Already defined variables are listed below: \n";
	for (Variable x : names) {
		cout << x.name << " = " << x.value; 
		if (x.constant)
			cout << "    constant ";
		cout << endl;
	}
	cout << " Already defined variables are listed above\n";
}

// global variable of class Symbol_table with declared variables 
//--------------------------------------------
Symbol_table symbols;

// mathematical operations part 1
//--------------------------------------------
int factorial(double number) {
	if (0 > number)
		error("Precondition: can not calculate factorial for number < 0");
	if (! is_integer(number))
		error("Precondition: can not calculate factorial for NOT int type");

	int result = 1;
	for (short counter = 2; counter <= number; counter++) {
		if (result > INT_MAX / counter) {
			cerr << "Calculating factorial of " << number << " = " << number << "! :\n";
			cerr << "While calculating factorial of " << counter << " = " << counter << "! :\n";
			cerr << 	" result = " << result << " INT_MAX = " << INT_MAX << " \n";
			cerr << "result > INT_MAX / counter that is " << result << " > " << INT_MAX / counter << "\n";
			cerr << "result * counter = " << result * counter << "\n";
			throw overflow_error("integer overflow error");
		}
		result = result * counter;
	}
	
	if (0 >= result)
		throw runtime_error("Final condition: result of factorial can not be number <= 0");
		
	return result;
}

// declarations of primary in calculator
double primary();

// mathematical operations part 2
//--------------------------------------------
int square_root() {
	Token t = ts.get_token_after_SPACE();
	ts.unget(t);
	if ('(' != t.kind && '[' != t.kind && '{' != t.kind) 
		error("number of square root must be in brackets");
	
	double x = primary();
	if (x < 0)
		error("Real solution for square root for number < 0 does not exist");
	if (false == is_integer(x))
		error("number of square root is not int type ");
	double result = sqrt(x);
	if (false == is_integer(result))
		error("result of square root is not int type ");
	
	return (int)result;
}

void print_power_overflow(int base, int exponent, const int LIMIT, int result, int counter) {
	string LIMIT_NAME;
	switch(LIMIT) {
		case INT_MAX:
			LIMIT_NAME = "INT_MAX";
			break;
		case INT_MIN:
			LIMIT_NAME = "INT_MIN";
			break;
		default:
			error("Unrecognized extremum ", LIMIT);
	}
	cerr << "Calculate " << base << " to power of " << exponent << " integer overflow :\n";
	cerr << "While calculating " << base << " to power of " << counter <<
		" result = " << result << " " << LIMIT_NAME << " = " << LIMIT << " \n";
	if (INT_MAX == LIMIT) {
		if (result > 0 && base > 0)
			cerr << "result > INT_MAX / base that is " << result << " > " << LIMIT / base << "\n";
		else if (result < 0 && base < 0)
			cerr << "result < INT_MAX / base that is " << result << " < " << LIMIT / base << "\n";
	}
	else {
		if (result < 0 && base > 0)
			cerr << "result < INT_MIN / base that is " << result << " < " << LIMIT / base << "\n";
		else if (result > 0 && base < 0)
			cerr << "result > INT_MIN / base that is " << result << " > " << LIMIT / base << "\n";
	}
		
	cerr << "result * base = " << result * base << "\n";
}

int power(int base, int exponent) {
	if (0 == base) {
		if (0 != exponent)
			return 0;
		error("0 to power of 0 is indeterminate form");
	}
	int result = 1;
	int last = exponent > 0 ? exponent : -exponent;
	
	//if (exponent < 0 && base != -1 && base != 1)
		//error("result of power is not int type ");
	
	for (int counter = 1; counter <= last; counter++) {
		if (result > 0 && base > 0) {
			if  (result > INT_MAX / base) {
				print_power_overflow(base, exponent, INT_MAX, result, counter);
				throw overflow_error("integer overflow error");
			}
		}
		else if (result < 0 && base < 0) {
			if (result < INT_MAX / base) {
				print_power_overflow(base, exponent, INT_MAX, result, counter);
				throw overflow_error("integer overflow error");
			}
		}
		else if (result < 0 && base > 0) {
			if (result < INT_MIN / base) {
				print_power_overflow(base, exponent, INT_MIN, result, counter);
				throw overflow_error("integer overflow error");
			}
		}
		else if (result > 0 && base < 0) {
			if (result > INT_MIN / base) {
				print_power_overflow(base, exponent, INT_MIN, result, counter);
				throw overflow_error("integer overflow error");
			}
		}
		
		result *= base;
	}
	
	if (exponent >= 0)
		return result;
	
	double result_double = 1. / result;
	if (false == is_integer(result_double))
		error("result of power is not int type ");
	result = result_double;
	
	return result;
}

// validate next token after token t to check calculator input data correction (to validate)
// in mathematical operations this function validate only sequence of input data
/* this function does not allow direct operations of the type: 
5!2 
6(7) 
(8)9
 which as a result create 2 separate expressions.
In order to create 2 separate expressions, separate these characters with a separator, e.g. new line, space or printing character:
5! 2 
6;(7)
(8) 9
*/
//--------------------------------------------
void validate_next_token(Token& t) {
	Token next_token = ts.get();
	char next = next_token.kind;
	switch (t.kind) {
		case '!':
			if ('(' == next || '[' == next || '{' == next || '=' == next ||
				number == next || NAME == next || SQRT == next || '!' == next || POWER == next)
				error("Next token after factorial token can not be opening bracket or \
number or variable or sqrt or ! or power or =");
			break;
		case number:
			if ('(' == next || '[' == next || '{' == next ||
				NAME == next || SQRT == next || POWER == next)
				error("Next token after number can not be opening bracket or variable or sqrt or power");
			break;
		case ')':
		case ']':
		case '}':
			if ('(' == next || '[' == next || '{' == next ||
				number == next || NAME == next || SQRT == next || POWER == next)
				error("Next token after closed bracket can not be number or variable or \
opening bracket or sqrt or power");
			break;
		case NAME:
			if ('(' == next || '[' == next || '{' == next || SQRT == next || POWER == next)
				error("Next token after variable can not be opening bracket or sqrt or power");
			break;
		default:
			error("Next token should not be check for ", t.kind);
			
	}
	ts.unget(next_token);
}

// global variables to check calculator input data correction (to validate)
// in mathematical operations this global variables validate only sequence of input data
//--------------------------------------------
short round_braces = 0;   // counter opening round braces - curly braces can not be inside square or round brackets
short square_braces = 0;  // counter opening square braces - square braces can not be inside round brackets

bool operation = false;      // can not accept sequence of +- +- ++ /- *+ *- and
// other mixes of 2 or more subsequent operators not separated by brackets

bool assignment_chance = false; // to signal assignment chance after detect first token as name of variable
bool assignment_done = false;   // when assignment has done to signal assignment completed and 
// other assignment can not exist in expression
short names_counter = 0;  // counter of variables to control number of variables before '='

int expression();

void token_error(const string& message, const Token& t);

char get_last_bracket_kind(char bracket_kind) {
	switch(bracket_kind) {
		case '(':
			return ')';
		case '[':
			return ']';
		case '{':
			return '}';
		default:
			token_error("Unrecognized opening bracket ", bracket_kind);
	}
}

void increment_bracket_kind(char bracket_kind) {
	switch(bracket_kind) {
		case '(':
			round_braces++;
			break;
		case '[':
			if (round_braces > 0) 
				error("Before close brace ), square brace [ is not accepted");
			square_braces++;
			break;
		case '{':
			if (square_braces > 0)
				error("Before close brace ], curly brace { is not accepted");
			if (round_braces > 0)
				error("Before close brace ), curly brace { is not accepted");
			break;
		default:
			token_error("Unrecognized opening bracket ", bracket_kind);
	}
}

void decrement_bracket_kind(char bracket_kind) {
	switch(bracket_kind) {
		case '(':
			round_braces--;
			break;
		case '[':
			square_braces--;
			break;
		case '{':
			break;
		default:
			token_error("Unrecognized opening bracket ", bracket_kind);
	}
}

int power() {
	int base, exponent;
	Token t = ts.get_token_after_SPACE();
	try {
		char bracket_kind = t.kind;
		char last_bracket = get_last_bracket_kind(bracket_kind);
		increment_bracket_kind(bracket_kind);
		base = expression();
		t = ts.get_token_after_SPACE();
		if (',' != t.kind)
			error("in power calculation after base and before exponent must be ", ',');
		exponent = expression();  
		t = ts.get_token_after_SPACE();
		if (t.kind != last_bracket) {
			ts.unget(t);
			error("closed bracket expected: ", last_bracket);
		}
		validate_next_token(t); 
		decrement_bracket_kind(bracket_kind);
	}
	catch(runtime_error& e) {
		if(t.kind == NEW_LINE)
			ts.unget(t);
		error(e.what() + string("\nIn power calculation base, ',' and exponent must be in only one pair of brackets \n"),
			"Syntax: pow(base, exponent) - base and exponent may be expression in brackets but \n\
base, ',' and exponent must be in only one pair of brackets after name of pow");
	}

	int result = power(base, exponent);
	return result;
}

int brackets_expression(char bracket_kind) {
	char last_bracket = get_last_bracket_kind(bracket_kind);
	increment_bracket_kind(bracket_kind);
	int result = expression();
	Token t = ts.get_token_after_SPACE();
	if (t.kind != last_bracket) {
		ts.unget(t);
		error("closed bracket expected: ", last_bracket);
	}
	validate_next_token(t); 
	decrement_bracket_kind(bracket_kind);

   return result;
}

void token_error(const string& message, const Token& t) {
	string name = get_name(t);
	if (name.size() > 0)
		error(message, name);
	else if (NAME == t.kind)
		error(message);
	else
		error(message, t.kind);
}

// calculator functions to token process and calculations
//-------------------------------------------- 
double primary() {
	double result;
	Token t = ts.get_token_after_SPACE();
	switch (t.kind) {
	case '(':
	case '[':
	case '{':
		result = brackets_expression(t.kind);
		operation = false;
		break;
	case '-':
	case '+':
		if (operation)
			error("Next token after operator can not be + or -");
		operation = true;
		result = '+' == t.kind ? primary() : -primary();
		break;
	case NEW_LINE:   // to allow many lines calculations without ignoring new line tokens in cin and Token_stream
		return primary();  // return before end of method to avoid set assignment_chance to false for first entered name
								// to allow assignment divided in many lines
	case number:
		result = t.value;
		operation = false;
		validate_next_token(t);
		break;
	case NAME:
		result = symbols.get_value(t.name);
		operation = false;
		validate_next_token(t);
		if(assignment_chance)
			names_counter++;
		return result;    // return before end of method to avoid set assignment_chance to false for first entered name
			
	default:
		token_error("unrecognized primary: ", t);
	}
	if(assignment_chance)
		assignment_chance = false;   // after get tokens other than NAME, assignment_chance is set to false
		
	return result;
}

int factor();

// before primary may be minus or sqrt
// skip plus because is not problematic for factorial calculations
// which order of operations has precedence over * / % - + 
double before_primary(Token& t, bool& minus_number) {
	double result;
	switch (t.kind) {
		case SQRT:
			result = square_root();
			break;
		case POWER:
			result = power();
			break;
		case '-':    // to allow minus '-' as first token in expression with factorial, which can not accept minus numbers
						 // and generate errors for -4! == -24  and  (-4)!
			if (operation)
				error("Next token after operator can not be + or -");
			minus_number = true;    // ok: -4! == -24    error: (-4)! 
			operation = true;
			t = ts.get_token_after_SPACE();
			result = before_primary(t, minus_number);
			break;
		default: 
			ts.unget(t);
			result = primary();
			if (false == minus_number && result == -(long)(INT_MIN))   // absolute value of labs(INT_MIN) == labs(INT_MAX + 1)
				error("Max number for int is ", INT_MAX);
			break;
	}
	return result;
}

// after primary may be tokens: power(exponent), factorial and assignment '='
// which order of operations has precedence over * / % - + 
double after_primary(double x, Token& t) {
	double result;
	switch (t.kind) {
		case '!':    // to allow minus '-' as first char in expression with factorial
			if (operation)
				error("Next token after operator can not be '!'");
			result = factorial(x);  // -4! == -24    (-4)! error
			validate_next_token(t);
			break;
		case '=': 
		if (! assignment_chance || assignment_done || names_counter > 1)
			error("Assignment operator must be only one in expression and \n must occur after variable name directly \
and if variable name is first primary in expression ");
				assignment_done = true;
				assignment_chance = false;
				result = expression();
				break;
		default:
			ts.unget(t);
			result = x;
	}
	return result;
}

// calculate of factors (square root, power, factorial)
// which order of operations has precedence over * / % - + 
int factor() {
	bool minus_number = false;
	Token t = ts.get_token_after_SPACE();
	double result = before_primary(t, minus_number);
	t = ts.get_token_after_SPACE();
	result = after_primary(result, t);
	if (minus_number)
		result = -result;
	if (false == is_integer(result))
		error("result is not int type");
	return (int)result;
}

// calculate of elements / % * (quotient, modulo quotient, product)
// which order of operations has precedence over - + 
int term() {
	double left = factor();
	operation = true;
	while (true) {
		Token t = ts.get_token_after_SPACE();
		switch (t.kind) {
		case '*':
			left *= factor();
			if (false == is_integer(left))
				error("result of multiplication is not int type ");
			break;
		case '/':
		case '%': {	
			int f = factor();
			if (f == 0) 
				error("divide by zero");
			if (t.kind == '%') 
				left = (int)left % f;
			else {
				 left /= f;
				 if (false == is_integer(left))
					error("result of division is not integer ");
			}	
			break;
		}
		default:
			ts.unget(t);
			return int (left);
		}
	}
}

// calculate of elements sums and differences
// which order of operations is the lowest
int expression() {
	operation = false;
	double left = term();
	operation = true;
	while (true) {
		Token t = ts.get_token_after_SPACE();
		switch (t.kind) {
		case '+':
			left += term();
			if (false == is_integer(left))
				error("result of addition is not int type ");
			break;
		case '-':
			left -= term();
			if (false == is_integer(left))
				error("result of subtraction is not int type ");
			break;
		default:
			ts.unget(t);
			return (int) left;
		}
	}
}

void reset_global_variables() {
	round_braces = 0;   // set counter opening round braces to 0 to cleaning before next operations
	square_braces = 0;  // set counter opening square braces to 0 to cleaning before next operations
	assignment_chance = false;
	assignment_done = false;
	names_counter = 0;
}

int declaration() {
	Token t = ts.get_token_after_SPACE();
	bool is_constant = ts.is_constant_token(t);
	ts.check_name(t);
	Token t2 = ts.get_token_after_SPACE();
	if (t2.kind != '=') {
		ts.unget(t2);
		error("= must be directly after only one variable name in declaration of ", t.name);
	}
	int value = expression();
	symbols.define_name(t.name, value, is_constant);
	return value;
}

int statement() {
	reset_global_variables();
	Token t = ts.get_token_after_SPACE();
	switch (t.kind) {
		case declaration_key:
			try {
				return declaration();
			}
			catch (Manual&) {
				error(HELP_NAME, " are keywords and can not be used as variable");
			}
		case NAME: {
			assignment_chance = true;
			ts.unget(t);
			int value = expression();
			if (assignment_done) 
				symbols.set_value(t.name, value);
			return value;
		}
		default:
			ts.unget(t);
			return expression();
	}
}

void clean_up_mess() {
	ts.ignore(print, NEW_LINE);
	reset_global_variables();
}

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

bool is_running() {
	Token t = EMPTY_TOKEN;
	bool skipping = true;
	do {
		t = ts.get();
		switch (t.kind) {
			case HELP:
				cout << manual.manual_string;
				cout << prompt;
				break;
			case VARS_VIEW:
				symbols.print_variables();
				cout << prompt;
			case NEW_LINE:
			case SPACE:
			case print:
				break;
			default:
				skipping = false;
		}
	} while(skipping);				
	if (t.kind == quit) 
		return false;
	else 
		ts.unget(t);
	return true;
}

void calculate() {
	int value = INT_MIN;
	do {
		try {
			cout << prompt;
			if (false == is_running())
				return;
			value = statement();             // separation in call of statement() and
			cout << result << value << endl; // call of cout to do not allow display string of result 
			                                 // before end of writing many lines calculations
		} 
		catch (runtime_error& e) {
			cerr << e.what() << endl;
			clean_up_mess();
		}
		catch (Manual& m) {
			system("clear");
			cout << manual.manual_string;
			clean_up_mess();
		}
	} while (true);
}

void enter_key(char key) {
	cout << "Enter \'" << key << "\' to continue ";
	char c;
	while (cin.get(c) && c != key)
		continue;
}

void add_predefined_variables() {
	symbols.define_name("k", 1000, false);
}

int main()
try {
	cout << "Welcome in simple calculator for only int type.\n";
	cout << manual.manual_string;
	add_predefined_variables();
	symbols.print_variables();
	calculate();
	return 0;
}
catch (exception& e) {
	cerr << "exception: " << e.what() << endl;
	enter_key(print);
	return 1;
}
catch (...) {
	cerr << "unrecognized exception\n";
	enter_key(print);
	return 2;
}

5

Uwagi w kolejności ich zobaczenia, nie wg istotności ani żadnego innego klucza. Kod przeglądałem raz, z góry na dół, bez wracania do już przeczytanego kodu, ani bez poświęcania większej ilości czasu na analizę.

  1. Wygodniej by było oceniającym przeczytać kod online — wrzuć go może na jakiegoś GitLaba albo gdzieś.
  2. Niekonsekwentne wcięcia — w linijce 11 masz trzy spacje, w 15 taba, w 20 cztery spacje itd.
  3. Czemu Token char kindsconstexpr, a Token program keywords to consty?
  4. Zasadniczo nie ma nic złego w takich funkcjach jak equals_case_insensitive, ale że korzystasz z nowoczesnego C++, to lepiej by się w tym miejscu sprawdziło std::equal z prostą lambdą.
  5. Do exist masz gotowiec w postaci std::find.
  6. Jeśli struct ma mieć jakąś walidację, to przyjęło się, by jednak robić z niego class, przy czym to jest kwestia stylu.
  7. Przemieszanie funkcji ze strukturami ujmuje czytelności.
  8. Nie bardzo rozumiem, czemu Manual jest strukturą, a nie po prostu stringiem.
  9. Wrzucenie całości w jeden plik ujmuje czytelności. Dobrze by było, by klasy były trzymane osobno.
  10. Masz trochę dziwny sposób przyjmowania liczb (Token_stream::get_number()). Nie łatwiej by było łapać stringa, odcinać ewentualny minus z początku i próbować konwertować resztę na longa i wysypywać się, kiedy ta konwersja nie wyjdzie?
  11. Ogólnie Twoje podejście do parsera jest nieco kulawe, ale tylko nieco, a to nie jest prosta sprawa. Jakbym pisał „dla efektu”, a nie „dla nauki”, to bym użył Boosta w wielu miejscach.
  12. Ogólne pomieszanie definicji funkcji, struktur i deklaracji zmiennych globalnych naprawdę utrudnia czytanie. Już na to narzekałem wcześniej, w punkcie siódmym, ale w 496 linijce osiągnąłem punkt krytyczny…
  13. Używanie double jako „takiego większego inta” potrafi kopnąć w tyłek. W szczególności, jeśli system na którym pracujesz korzysta z modelu LP64, jak to czynią m.in. 64-bitowe Linuksy czy MacOS-y, to double będzie 64-bitowy i long będzie 64-bitowy, 2 397 083 434 877 565 865 nie będzie miał wiernej reprezentacji.
  14. Tak na przyszłość, są biblioteki do obsługi arbitralnie dużych intów oraz takie, które mają wbudowane sprawdzanie przepełnienia zmiennej. Wiem że tutaj piszesz „dla nauki”, ale i tak zaznaczę, jakbyś kiedyś pisał „dla efektu”.
  15. Już zdążyłem zapomnieć, co to było primary, a tutaj mam linijkę 526… To jest podstawowy i bardzo poważny problem z Twoim kodem. Przez niego zapewne nie udało mi się wykryć innych problemów.
  16. A tak swoją drogą, to mógłbyś, jeśli byś wolał, zostawić te inty, pisać wszystko na double’ach, i tylko patrzyć na flagi sprzętowe celem wykrywania overflowów i reszty podobnych problemów. Nie mówię że masz woleć, ale jest to opcja…
  17. A, ja nie zapomniałem co to jest primary, Ty go po prostu zdefiniowałeś 278 linijek niżej, niż go stworzyłeś… To bardzo zła konwencja, wymuszasz przy czytaniu kodu latanie z góry na dół i szukanie różnych rzeczy…
  18. int factor() 🤷
  19. before_primaryafter_primary mają dziwne nazwy, które nie oddają ich celu.
  20. Nazewnictwo funkcji dalej też pozostawia wiele do życzenia, np. nie wiem czemu int expression() „calculate of elements sums and differences, which order of operations is the lowest”. Ani tak naprawdę co miałeś na myśli w tym komentarzu…
  21. Jak masz takie funkcje jak reset_global_variables, to znak, że miałoby sens wydzielenie tych „global variables” do osobnej struktury i danie im tej funkcji jako metody. A potem zastanowienie się, czy to powinny być zmienne globalne.
  22. int declaration() 🤷
  23. int statement() 🤷
  24. clean_up_mess() wygląda jakby chciało być czyimś destruktorem. Używasz licznie wyjątków w swoim kodzie — co jeśli na którejś z rozlicznych możliwych ścieżek wykonania nie „czyścisz bałaganu”, a powinieneś? Jesteś pewien, że nigdy tego nie zapominasz tam, gdzie jest potrzebne?

Ogólnie — funkcje przynajmniej masz krótkie, ale i ich nazewnictwo nie powala. Zaczynało się dobrze, a potem coraz gorzej — jeśli gdzieś musisz mieć komentarz tłumaczący co funkcja robi i po co istnieje, to niezawodny znak, że należałoby tę funkcję nazwać lepiej. Organizacja kodu to sieczka, tutaj najwięcej jesteś w stanie zdziałać celem poprawy jakości całości. Piszesz bardzo w „C+” zamiast w C++, korzystając z bardzo niewielu rzeczy ponad C — co nie jest niczym złym, ale w wielu miejscach korzystanie z nowinek C++-owych pomogłoby Ci z przepływem sterowania i ogólnie logiką kodu, a w kilku innych z prostotą poszczególnych rozwiązań.

Zatem, odpowiadając na Twoje pytanie „Przede wszystkim czy ten kod dobrze sie czyta dla innej osoby” — nie. Bardzo nie. Widziałem w życiu, co prawda, gorszy kod, ale gdy tak się działo, to mi rosło istotnie spożycie alkoholu… Podejrzewam na szczęście, że jesteś w stanie nie aż tak dużym wysiłkiem poprawić jego czytelność wielokrotnie.

6

Zawsze mnie bawi jak widzę:

    if (.....)
        return true;
    else
        return false;

nie wystarczy:

    return .....;
0

@Althorion:

  1. zaloze nowy temat, konto na bitbucket i tam wrzuce takze ktores z wiekszych zadanek jakie pisalem w Java, bash, bo w C++ ten kalkulator na razie jest najwiekszy z programow pisanych w C++ preze mnie.
  2. dlatego const bo nie kompilowal sie kod dla constexpr string, ale juz dla constexpr char* sie kompilowal, ale wolalem robic mniej zamieszania bo go w moim kodzie jest zbyt duzo i zostalem przy const string.
  3. lambd jeszcze w c++ nie przerabialem, juz chyba szybciej zaczne przerabiac strumienie w Javie ale tez jeszcze nie mialem okazji - programowanie funkcyjne u mnie nie rozpoczete, obiektowe dopiero zaczete, a strukturalne tez nie wypada dobrze
  4. nie przejrzalem dokumentacji vector
  5. rzeczywiscie masz racje bo teraz zamiast catch (Manual& m) { zastosowalem catch (string& m) { i dziala, choc moim zdaniem ujmuje to troche czytelnosci
  6. w Java i czystym c juz pisalem programy podzielone na wiele plikow, w C++ dopiero zaczne niebawem
  7. Nie chce konwertowac na long bo chcialem tez wczytac liczby double tylko dla podgladu
  8. jest kulawe ale i tak natrudzilem sie przy tym a wiele razy cos nie dzialalo jak zakladalem a nawet i blednie zakladalem ze rozumiem elementy skladni i biblioteki C++ a czasem okazywalo sie co innego, czesto nic na ekranie sie nie pojawialo bo wczytywalem za duzo Tokenow i je gubilem przez to - w java byloby mi latwiej to wykryc, bo tam automatyczne wyjatki z maszyny wirtualnej informuja mnie o roznych awariach a w C++ tak jak w C nie spotkalem sie z tym zjawiskiem, ale i tak w C jest wieksza meczarnia niz w c++ bo nie ma try{} catch{}, tylko goto i longjmp albo return i errno - ogolnie czysty ANSI C jest duzo mniej czytelny niz C++ i Java.
    O Boost to tylko wiem ze istnieje i nic ponad to, jeszcze nie znam skladni jezyka C++ i biblioteki standardowej to gdzie mi tam do jakis niestandardowych zewnetrznych rzeczy
  9. nie spodziewalem sie tego, wiem ze w Javie long zawsze wiecej zajmuje niz int, bo to ustawienie JVM, w C to zgaduje ze jest taka samo jak w C++ bo to jezyki bez maszyny wirtualnej kompilowane do kodu maszynowego ?
  10. nie chce na razie korzystac z czegos wiecej ponad standardowe:
    skladnie, instrukcje, typy wbudowane, biblioteki jezyka C++, C, Java, bash,
    bo i tak tych standardowych to ledwo poznalem moze w 1-10% oprocz ANSI C , bo to maly jezyk i mala biblioteka ale i tak moze z 50% jezyka ANSI C uzywalem
  11. w tym kodzie jest pewnie wiecej problemow, ja pomimo wielokrotnego testowania na roznych danych i wielu obliczen oraz weryfikacji w innych internetowych kalkulatorach nie jestem do konca pewien czy moj kalkulator dziala OK
  12. nie wiem co to flagi sprzetowe
  13. nazewnictwo i komentarze sa slabe bo ja po angielsku takze slabo mowie, troche lepiej pisze i czytam ale to i tak slabo
  14. destruktorow jeszcze nie uzywalem
3

Czemu Ty sie uczysz wielu jezykow jednoczesnie? I to bedac poczatkujacym?
Sprobowanie wielu jezykow jest jak najbardziej na plus (ja bym nawet powiedzial, ze dla swiadomego programisty to powinien byc obowiazek) ale nie w taki skrajny sposob.

3

zaloze nowy temat, konto na bitbucket i tam wrzuce takze ktores z wiekszych zadanek jakie pisalem w Java, bash, bo w C++ ten kalkulator na razie jest najwiekszy z programow pisanych w C++ preze mnie.

Dobry pomysł.

dlatego const bo nie kompilowal sie kod dla constexpr string, ale juz dla constexpr char* sie kompilowal, ale wolalem robic mniej zamieszania bo go w moim kodzie jest zbyt duzo i zostalem przy const string.

constexpr std::string jest od C++20, a na razie, moim zdaniem, osiągnąłeś w ten sposób więcej zamieszania — chyba że jest jakiś powód, którego nie widzę, przez który faktycznie uzyskujesz lepszy rezultat w constexpr char tam wcześniej i oba nie mogą być po prostu constami.

lambd jeszcze w c++ nie przerabialem, juz chyba szybciej zaczne przerabiac strumienie w Javie ale tez jeszcze nie mialem okazji - programowanie funkcyjne u mnie nie rozpoczete, obiektowe dopiero zaczete, a strukturalne tez nie wypada dobrze

Spoko. Tylko zaznaczyłem jako potencjalną ścieżkę rozwoju.

nie przejrzalem dokumentacji vector

Przejrzyj. Przy czym akurat tego tam AFAIK nie ma, jest gdzie indziej schowane…

w Java i czystym c juz pisalem programy podzielone na wiele plikow, w C++ dopiero zaczne niebawem

To trochę brzmi jak „nie zacząłem jeszcze używać obu nóg podczas jazdy na rowerze, kręcę tylko jedną, ale zacznę niebawem” — nie widzę praktycznego powodu utrudniania sobie w ten sposób życia. Nawet do celów edukacyjnych…

jest kulawe ale i tak natrudzilem sie przy tym a wiele razy cos nie dzialalo jak zakladalem a nawet i blednie zakladalem ze rozumiem elementy skladni i biblioteki C++ a czasem okazywalo sie co innego, czesto nic na ekranie sie nie pojawialo bo wczytywalem za duzo Tokenow i je gubilem przez to - w java byloby mi latwiej to wykryc, bo tam automatyczne wyjatki z maszyny wirtualnej informuja mnie o roznych awariach a w C++ tak jak w C nie spotkalem sie z tym zjawiskiem, ale i tak w C jest wieksza meczarnia niz w c++ bo nie ma try{} catch{}, tylko goto i longjmp albo return i errno - ogolnie czysty ANSI C jest duzo mniej czytelny niz C++ i Java.

Oj tak. I, jak pisałem, tworzenie parserów się tylko wydaje łatwe, a potem jest przypadek szczególny za przypadkiem szczególnym… Poszło Ci całkiem nieźle jak na pierwszy raz, pomimo ogólnego burdelu w kodzie logika jest całkiem w porządku.

nie spodziewalem sie tego, wiem ze w Javie long zawsze wiecej zajmuje niz int, bo to ustawienie JVM, w C to zgaduje ze jest taka samo jak w C++ bo to jezyki bez maszyny wirtualnej kompilowane do kodu maszynowego ?

Tak, mniej-więcej. Masz pewne gwarancje co do tego, jakich rozmiarów są typy, ale są one nader słabe i zależne od sprzętu; a i tak jakbyś pisał na coś naprawdę dzikiego to byś spotykał kompilatory działające niezgodnie ze standardem (np. widziałem taki, gdzie short był mniejszy od chara).

w tym kodzie jest pewnie wiecej problemow, ja pomimo wielokrotnego testowania na roznych danych i wielu obliczen oraz weryfikacji w innych internetowych kalkulatorach nie jestem do konca pewien czy moj kalkulator dziala OK

To nie dziwi. Weryfikacja działania parserów jest wybitnie trudna, szczególnie przy braku wykorzystania zaawansowanych metod.

nie wiem co to flagi sprzetowe

Poczytaj w takim razie w wolnej chwili o tym, jak działają liczby zmiennoprzecinkowe. Dla bezpieczeństwa schowaj wcześniej łatwo tłukące się przedmioty.

destruktorow jeszcze nie uzywalem

W nich tkwi podstawowa przewaga C++ nad C i w ogóle jeden z podstawowych selling pointów języka — są konieczne do osiągnięcia RAII, które z kolei jest podstawową techniką operowania zasobami w językach obiektowych bez automatycznego zarządzania pamięcią.

2

Jeśli mogę coś dodać do ogólnej struktury, dużym problemem jest to, że łączysz rolę parsera z ewaluacją wyrażeń.
Jak to więc zrobić poprawnie? Najproście by było użyć jakiegoś generatora parserów dla gramatyk. Ja używałem
https://bnfc.digitalgrammars.com/

Musisz sobie wtedy napisać gramatykę dla własnego kalkulatora. BNFC wygeneruje Tobie pliki .cpp w których jest już zaimplementowane sprawdzanie poprawności składniowej - jak dostaniesz stamtąd błąd, to jest błąd składniowy. Jak nie, to wszystko jest poprawnie. Można się wtedy zająć samą ewaluacją wyrażeń. Nie tylko masz lepsze rozgraniczenie odpowiedzialności (bo jednak lekser, parser, i ewaluator to są trzy odrębne komponenty), ale też kod staje się znacznie krótszy i czytelniejszy.

1 użytkowników online, w tym zalogowanych: 0, gości: 1