In this exercise I am using Visual Studio Community 2017 and the header file "std_lib_facilities.h" which can be found here:
http://www.stroustrup.com/Programming/PPP2code/std_lib_facilities.h
Chapter 8 // Exercise 1
1. Modify the calculator program from Chapter 7 to make the input stream an explicit parameter (as shown in section 8.5.8), rather than simple using cin. Also give the Token_stream constructor (section 7.8.2) an istream& parameter so that when we figure out how to make out own istream (e.g., attached to files), we can use the calculator for those. Hint: Don't try to copy an istream.
// pandp.cpp : Defines the entry point for the console application.
//
#include "stdafx.h"
#include "std_lib_facilities.h"
#include <windows .h>
//user defined type to hold name-value pair for use in calculator
struct Token {
char kind;
double value;
string name;
Token(char ch) :kind(ch), value(0) { }
Token(char ch, double val) :kind(ch), value(val) { }
Token(char ch, string n) :kind(ch), name(n) { }
};
//user-defined type that handles retrieving items from input
class Token_stream {
bool full;
Token buffer;
public:
Token_stream() :full(0), buffer(0) { } //default constructor
Token_stream(istream&); //constructor for istream
Token get();
void unget(Token t) { buffer = t; full = true; }
void ignore(char);
};
double expression(Token_stream& ts); //forward declaration
void calculate(Token_stream& ts);
void showHelp();
const char let = 'L';
const char quit = 'Q';
const char print = '\n';
const char number = '8';
const char name = 'a';
const char squareR = 's';
const char findPow = 'p';
const char isConst = 'C';
const char help = 'h';
const string declKey = "let";
const string quitKey = "quit";
const string sqrtKey = "sqrt";
const string powKey = "pow";
const string constKey = "const";
const string printKey = "'\n'";
//evaluate each char in the stream and determine what it is
Token Token_stream::get()
{
if (full) //check if we have already have a token ready
{
full = false;
return buffer;
}
char ch;
cin.get(ch); //does not skip whitespace
while (isspace(ch)) //if ch is whitespace
{
if (ch == '\n') //if ch == newline
return Token(print); //print result
cin.get(ch); //if not newline get next ch in stream
}
switch (ch)
{
case '(': case ')': case '+': case '-':
case '*': case '/': case '%':
case '=': case ',':
return Token(ch); //let each char represent itself
case '.':
case '0': case '1': case '2': case '3':
case '4': case '5': case '6': case '7':
case '8': case '9':
{
cin.unget(); //put digit back into the input stream
double val;
cin >> val; //read a floating-point number
return Token{ number, val }; //return number or . with a value, put back into buffer
}
//allow user defined variables if user types #
case '#':
return Token(let);
//if user presses h or H return to execute help function
case 'h': case 'H':
return Token(help);
default:
//do this if ch is a letter
if (isalpha(ch) || ch == '_')
{
string s;
s += ch;
//while there are still chars in cin, read them into s
while (cin.get(ch) && (isalpha(ch) || isdigit(ch) || ch == '_'))
s += ch;
cin.unget();
//if string is equal to other commands defined below, return them
if (s == declKey)
return Token(let);
if (s == constKey)
return Token(isConst);
if (s == quitKey)
return Token(quit);
if (s == sqrtKey)
return Token(squareR);
if (s == powKey)
return Token(findPow);
return Token(name, s);
}
//if the char does not fit any of these paramenters return an error message
error("Bad token");
}
}
//discard characters up to and including a c
//c represents the kind of token
void Token_stream::ignore(char c)
{
//first look in the buffer
if (full && c == buffer.kind)
{
full = false;
return;
}
full = false;
//now search input
char ch;
while (cin >> ch)
if (ch == c) return;
}
//-----------------------------------------------------------------------------------------------------//
//-----------------------------------------------------------------------------------------------------//
struct Variable
{
string name;
double value;
bool isConst;
Variable(string n, double v, bool ic) :name(n), value(v), isConst(ic) { }
};
//-----------------------------------------------------------------------------------------------------//
class Symbol_table {
vector<Variable> var_table;
public:
double get(string s);
void set(string s, double d);
bool is_declared(string s);
double define(string var, double val, bool isConst);
double declare(Token_stream& ts);
};
//return the value of the Variable named s
double Symbol_table::get(string s)
{
for (int i = 0; i < Symbol_table::var_table.size(); ++i)
{
if (Symbol_table::var_table[i].name == s)
{
return Symbol_table::var_table[i].value;
}
}
error("get: undefined name ", s);
}
//set the Variable named s to d
void Symbol_table::set(string s, double d)
{
for (int i = 0; i < Symbol_table::var_table.size(); ++i)
{
//allow redefinitions as long as variable isn't const
if (Symbol_table::var_table[i].name == s && Symbol_table::var_table[i].isConst == false)
{
Symbol_table::var_table[i].value = d;
return;
}
}
error("set: undefined name ", s);
}
//is variable already declared?
bool Symbol_table::is_declared(string s)
{
for (int i = 0; i < Symbol_table::var_table.size(); ++i)
{
if (Symbol_table::var_table[i].name == s && Symbol_table::var_table[i].isConst == true)
error("Cannot reassign const variable");
else if (Symbol_table::var_table[i].name == s && Symbol_table::var_table[i].isConst == false)
return true;
}
return false;
}
//allow programmers to add (var,val) to variable vector
double Symbol_table::define(string var, double val, bool isConst)
{
if (is_declared(var))
error(var, " declared twice");
var_table.push_back(Variable(var, val, isConst));
return val;
}
//check for name definition errors
double Symbol_table::declare(Token_stream& ts)
{
Token t = ts.get();
//is const the next word in the stream?
bool isC;
if (t.kind == isConst)
{
isC = true;
t = ts.get(); //get the the next word in the stream for the name
}
else
isC = false;
if (t.kind != name)
error("name expected in declaration;");
string name = t.name;
//if name has already been declared ask if they want to change it
if (Symbol_table::is_declared(name))
{
cout << name + ", declared twice. Would you like to reassign? (No need to print with ';') y/n > ";
cin.clear();
cin.ignore(10000, '\n'); //clear the buffer
string ans;
getline(cin, ans);
if (ans == "n")
error(name, ", will not be reassigned; ");
if (ans == "y")
{
cout << "(No need to print with ';') Please enter new value: ";
int val;
cin >> val;
Symbol_table::set(name, val);
double d = val; //return value to print to reset calculator
return d;
}
}
Token t2 = ts.get();
if (t2.kind != '=')
error("= missing in declaration of ", name);
double d = expression(ts);
Symbol_table::var_table.push_back(Variable(name, d, isC));
return d;
}
//-----------------------------------------------------------------------------------------------------//
Symbol_table symbols;
//-----------------------------------------------------------------------------------------------------//
//check tokenstream for 'your char here'
Token checkForChar(Token t, char ch)
{
if (t.kind != ch)
{
//convert ch to string for error message
string chstring = "";
chstring += ch;
error("'" + chstring + "' expected");
}
return t;
}
//solve characters received from ts.get()
double primary(Token_stream& ts)
{
//get character from stream
Token t = ts.get();
switch (t.kind)
{
//solve "(expression)"
case '(':
{
double d = expression(ts);
t = ts.get();
checkForChar(t, ')');
return d;
}
//solve "-primary"
case '-':
return -primary(ts);
//solve "number"
case number:
return t.value;
//solve "name"
case name:
return symbols.get(t.name);
//solve "sqrt(expression)"
case squareR:
{
//get next char after 'sqrt' if not '(' then error
t = ts.get();
checkForChar(t, '(');
//if expression is less than 0 print an error
double d = expression(ts);
if (d < 0)
error("Cannot squareroot negative integers");
//get next char after expression, if not ')' then error
t = ts.get();
checkForChar(t, ')');
// return square root of the expression taken from the tokenstream
return sqrt(d);
}
//solve "pow(expression, expression)"
case findPow:
{
//get next char after 'pow' if not '(' then error
t = ts.get();
checkForChar(t, '(');
//get the expression after '('
double d = expression(ts);
//get next char after 'expression' if not ',' then error
t = ts.get();
checkForChar(t, ',');
//get the expression after ','
double i = expression(ts);
//get next char after expression, if not ')' then error
t = ts.get();
checkForChar(t, ')');
// return expression using pow() from
return pow(d, i);
}
default:
error("primary expected");
}
}
//solves for primary, *, / and %
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 d = primary(ts);
if (d == 0)
error("%:divide by zero");
left = fmod(left, d);
break;
}
default:
ts.unget(t);
return left;
}
}
}
//solves for terms and + -
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;
}
}
}
double statement(Token_stream& ts)
{
Token t = ts.get();
switch (t.kind)
{
case let:
return symbols.declare(ts);
default:
ts.unget(t);
return expression(ts);
}
}
void clean_up_mess()
{
cout << "\nRestarting Calculator .";
Sleep(400);
cout << ".";
Sleep(400);
cout << ".";
Sleep(400);
cout << ".";
Sleep(400);
cout << ".\n\n";
cin.clear();
cin.ignore(10000, '\n'); //clear the buffer
}
const string prompt = "> ";
const string result = "= ";
void calculate(Token_stream& ts)
{
while (true) try
{
cout << prompt;
Token t = ts.get();
while (t.kind == print)
t = ts.get(); //first discard all 'prints'
//if user types h or H show help, then clear the stream and get new values
if (t.kind == help)
{
showHelp();
cout << prompt;
t = ts.get();
}
if (t.kind == quit)
return;
ts.unget(t);
cout << result << statement(ts) << endl;
}
catch (runtime_error& e)
{
cerr << e.what() << endl;
clean_up_mess();
}
}
int main()
try {
symbols.define("pi", 3.1415926535, true);
symbols.define("e", 2.7182818284, true);
symbols.define("k", 1000, false);
Token_stream ts;
calculate(ts);
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;
}
void showHelp()
{
cout << "-----------------------HOW TO USE THE CALCULATOR------------------------\n";
cout << "------------------------------------------------------------------------\n";
cout << "1. To exit the program type 'quit' and press enter.\n";
cout << "2. To show results, press enter.\n";
cout << "3. To create your own keyword, type 'let yourWord = value'\n";
cout << " You can let a keyword be a const by typing 'const' before 'let'\n";
cout << "4. To find the squareroot, type 'sqrt' followed by your number.\n";
cout << "5. To find a number to a power of, type 'pow(number,number)'\n";
cout << "------------------------------------------------------------------------\n\n";
cin.clear();
cin.ignore(10000, '\n'); //clear the buffer for new values
}