Pages

Sunday, 8 April 2018

Chapter 8 // Exercise 1 - Principles & Practice Using C++

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
}

No comments:

Post a Comment