// Calculator.cpp
#include "Calculator.h"
//------------------------------------------------------------------------------
const char let = '#';
const char quit = 'Q';
const char print = ';';
const char number = '8';
const char name = 'a'; // variable var_table
const char square_root = 'r';
const char power = 'p';
const char constant = 'c';
const char help = 'H';
//------------------------------------------------------------------------------
Token Token_stream::get()
{
if (full) { full=false; return buffer; }
char ch;
inputstream.get(ch);
if (!inputstream) return Token(quit); // if EOF - quit; without this string code will crash
if(isspace(ch)) { // handle with spaces
if (ch == '\n') return Token(print);
inputstream>>ch;
}
switch (ch) {
case print:
case '(':
case ')':
case '+':
case '-':
case '*':
case '/':
case '%':
case '=': // not used yet
case ',': // comma used to separete arguments in pow(argument_X,argument_Y);
case '#': // new "let"
return Token(ch);
case '.':
case '0': case '1': case '2': case '3': case '4':
case '5': case '6': case '7': case '8': case '9':
{ inputstream.unget();
double val;
inputstream >> val;
return Token(number,val);
}
default: // used to define var_table
if (isalpha(ch)) { // returns true if ch is a letter, isdigit(ch) - return true if ch is a digit
string s;
s += ch;
while(inputstream.get(ch) && (isalpha(ch) || isdigit(ch) || ch=='_') ) s += ch;
inputstream.unget();
if (s == "let") return Token(let); // "let" name "=" expression
if (s == "const") return Token(constant);
if (s == "exit" || s == "quit") return Token(quit);
if (s == "sqrt") return Token(square_root);
if (s == "pow") return Token(power);
if (s == "help" || s == "h" || s == "H" || s == "Help") return Token(help);
return Token(name,s);
}
error("Bad token");
}
}
//------------------------------------------------------------------------------
void Token_stream::ignore(char c) // provides performance cycle of calculating, through skipping wrong inputted symbols, used in the catch-block; c = ';' (print)
{ // handle with set of wrong and right statements, for input: "wrong_statement; right_statement; wrong_statement;" output will be:
if (full && c==buffer.kind) { // "error; result; error " (if right_statement is not a declaration );
full = false;
return;
}
full = false;
char ch;
while (inputstream.get(ch))
if (ch==c || isspace(ch)) return;
}
//------------------------------------------------------------------------------
Table Symbol_table;
//------------------------------------------------------------------------------
double Table::get_value(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);
}
//------------------------------------------------------------------------------
void Table::set_value(string s, double d)
{
for (int i = 0; i<var_table.size(); ++i)
if (var_table[i].name == s && !var_table[i].constant) {
var_table[i].value = d;
return;
}
else
if (var_table[i].name == s && var_table[i].constant)
error("set: attempt to redefine a constant");
error("set: undefined name ",s);
}
//------------------------------------------------------------------------------
bool Table::is_declared(string s)
{
for (int i = 0; i<var_table.size(); ++i)
if (var_table[i].name == s) return true;
return false;
}
//------------------------------------------------------------------------------
double Table::declare(Token_stream& ts) // declares a variable; new grammatics unit declaration is "let" var_name " = " expression;
{
bool is_constant = false;
Token t = ts.get();
if (t.kind == constant)
is_constant = true;
else
ts.unget(t);
t = ts.get();
if (t.kind != name) error ("name expected in declaration");
string name = t.name;
if (is_declared(name)) error(name, " declared twice");
Token t2 = ts.get();
if (t2.kind != '=') error("= missing in declaration of " ,name);
double d = expression(ts);
var_table.push_back(Variable(name,d,is_constant));
return d;
}
//------------------------------------------------------------------------------
double x_power_y(Token_stream& ts)
{
Token t = ts.get();
if (t.kind != '(') error("x_power_y: '(' expected");
double number = expression(ts);
t = ts.get();
if (t.kind != ',') error("x_power_y:',' exptected.");
double power = expression(ts);
t = ts.get();
if (t.kind != ')') error("x_power_y:')' expected");
int pow = power;
if (pow != power) error("x_power_y: NON-integer power!");
if (pow < 0) error ("x_power_y: Negative power!");
if (pow == 0) return 1;
double multiplier = number;
for (int i = 1; i < pow; i++)
number *= multiplier;
return number;
}
//------------------------------------------------------------------------------
double primary(Token_stream& ts)
{
Token t = ts.get();
switch (t.kind) {
case '(':
{ double d = expression(ts);
t = ts.get();
if (t.kind != ')') error("')' expected");
Token next = ts.get();
if (next.kind == number)
d *= next.value;
else
ts.unget(next);
return d;
}
case '-':
return - primary(ts); // handle "-2+3++3;" input
case '+':
return primary(ts);
case number: // handle multipliers for names, e.g input: #x = 3; 3x; output: =3; =9;
{
Token next = ts.get();
if(next.kind == name)
return t.value * Symbol_table.get_value(next.name);
else
ts.unget(next);
return t.value;
}
case name: // access to variable by its name
{
double d = Symbol_table.get_value(t.name);
string lvalue = t.name;
t = ts.get();
if (t.kind == '=') { // if assigment operator goes after name
double rvalue = expression(ts);
Symbol_table.set_value(lvalue, rvalue);
return Symbol_table.get_value(lvalue);
}
else
ts.unget(t);
return d;
break;
}
case square_root:
{
double d = primary(ts);
if (d>=0) return sqrt(d);
error("Can't calculate square root of a negative number.");
break;
}
case power: // pow(number,power)
return x_power_y(ts);
break;
case help:
calc_help(ts.get_ostream());
break;
default:
error("primary expected");
}
}
//------------------------------------------------------------------------------
double term(Token_stream& ts)
{
double left = primary(ts); //
while(true) {
Token t = ts.get();
switch(t.kind) {
case '*':
left *= primary(ts);
break;
case '/':
{ double d = primary(ts);
if (d == 0) error("divide by zero");
left /= d;
break;
}
case '%':
{
double right = primary(ts);
int i1 = left;
int i2 = right;
if (i1 != left) error("Left operand of % isn't an integer number");
if (i2 != right) error("Right operand of % isn't an integer number");
if (i2 == 0) error("Deviding by zero.");
left = i1%i2;
break;
}
case '(': // handle missed '*', e.g. x(x-y)(x+y)
case name:
case square_root:
case power:
ts.unget(t);
left *= primary(ts);
break;
default:
ts.unget(t);
return left;
}
}
}
//------------------------------------------------------------------------------
double expression(Token_stream& ts)
{
double left = term(ts);
while(true) {
Token t = ts.get();
switch(t.kind) {
case '+':
left += term(ts);
break;
case '-':
left -= term(ts);
break;
default:
ts.unget(t);
return left;
}
}
}
//------------------------------------------------------------------------------
void predefine() // define constants
{
Variable kilo("k",1000.0,true);
Symbol_table.pushback(kilo);
}
//------------------------------------------------------------------------------
double declaration(Token_stream& ts)
{
return Symbol_table.declare(ts);
}
//------------------------------------------------------------------------------
double statement(Token_stream& ts) // new grammatics unit, that forking to declarations (of variables) or expressions; statement is declaration or expression;
{
Token t = ts.get();
switch(t.kind) {
case let:
return declaration(ts);
default:
ts.unget(t);
return expression(ts);
}
}
//------------------------------------------------------------------------------
void clean_up_mess(Token_stream& ts) // handle with mistakes, proceeding permanent perfomance
{
ts.ignore(print);
}
//------------------------------------------------------------------------------
void calc_help(ostream& ost)
{
ost << "\n*HELP*\n";
ost << "This calculator supports:\n\n"
" - ordinary arithmetic operations and dividing by mod, thats usage is x%y;\n"
"where x and y are integer numbers.\n"
"Exaple of input:\n15/(7-5+1)%2+9\noutput:\t= 10\n\n"
" - power computation, thats usage is pow(x,y), where x is a number\n"
"and y - power, y - must be an integer number.\n"
"Exaple of input:\npow(3.3,4)\noutput:\t= 118.592\n\n"
" - square root computation, thats usage is sqrt(x), where x >= 0\n"
"Exaple of input:\nsqrt(10.1)\noutput:\t= 3.1785\n\n"
" - variables.\nTo define a variable use word let or '#', followed by name\n"
"of variable, assigment operator and value or expression, e.g:\n"
"input: let var_1 = 100; #var_2 = var_1-10;\n"
"\nVariable name must begin with a character and may contain characters,\n"
"digits and symbol of underscore '_'\n"
"To reassign variable use assign operator '=', e.g:\n"
"let x = 1; let y = x; x = 9; y = 1/x\n"
"\nTo declare a constant use \"let const variable_name = value\", e.g:\n"
"let const pi = 3.14\n"
"\nNotice that attempt to reassign a constant will return error.\n"
"\nPriority of operators:\n\t- parentheses '(' ')' have highest priority;"
"\n\t- '*', '/', '%', sqrt(x), pow(x,y) have equal middle priority;"
"\n\t- '+' and '-' have equal lowest priority.\n"
"\nTo calculate expressions press Enter or input ';'.\n"
"\nType 'q', 'quit' or 'exit' to exit and 'h','help', 'H' or 'Help' to see \n"
"this help documentation.\n";
}
//------------------------------------------------------------------------------
const string prompt = "> ";
//------------------------------------------------------------------------------
const string result = "= ";
//------------------------------------------------------------------------------
void calculate(istream& ist, ostream& ost) // cycle of calculating with errors processing
{
predefine();
Token_stream ts(ist,ost);
ost << "Use 'help' for instructions of input.\n";
while(true)
try {
ost << prompt;
Token t = ts.get();
while (t.kind == print) t=ts.get();
if (t.kind == quit) return;
ts.unget(t);
double d = statement(ts);
if (d == d) // checking of NaN (its cause when calc_help() called from primary)
ost << result << d << endl; // << fixed << setprecision(4)
}
catch(runtime_error& e) {
ost << e.what() << "\nEnter to proceed." << endl;
clean_up_mess(ts);
}
}