Pages

Sunday, 18 March 2018

Chapter 7 // Exercise 6 - 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 7 // Exercise 6



6. Part of what every program should do is to provide some way of helping its user. Have the calculator print out some instructions for how to use the calculator if the user presses the H key (both upper and lowercase).



Originally I thought this was going to take me 10 minutes, it ended up taking me 90. Mainly because the implementation showed up a few fatal errors in my previous code. One was that I had been calling calculate() to simply 'restart' the program, completely forgetting that after the function had executed it would jump right back to where it left off; creating errors. The calculator does restart itself however it returns an error message which would confuse people as they wouldn't have done anything wrong. I've now changed this error in previous posts as well.

For this exercise though, I created a new const char called help assigned with 'h'. I then added a case in ts.get() which check for 'h' or 'H' and returned Token(help) if found. In calculate() I realised it was important where checking for help was. Before the while loop and obviously it will only know what to do for 'h' or 'H' on the first iteration of the loop. If placed after the check for 'quit' then the loop wouldn't know what to do if the user typed quit after help. So the check for help must go in-between the while loop and quit. 

This worked just fine until I realised that an error was returning after help because it will still checking the stream for characters. To solve this ts.get() is called again in the same block to prevent an error being thrown that a primary was expected.

I also decided to jazz up cleanupmess() by making it look like the computer was actually restarting the program by using a windows function Sleep(), which pauses the program for a given amount of milliseconds. I also added some code which cleared the buffer stream as before it was making the program crash by not actually cleaning anything up...perhaps that was an error I should've noticed in the drill...


// 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) { }

 Token get();
 void unget(Token t) { buffer = t; full = true; }

 void ignore(char);
};

double expression();  //forward declaration
void calculate();
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;
}

//-----------------------------------------------------------------------------------------------------//
Token_stream ts;
//-----------------------------------------------------------------------------------------------------//

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();
};

//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; < 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 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();
 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;
}

//read in values that are accepted for first time use
double primary()
{
 //get character from stream
 Token t = ts.get();
 switch (t.kind) 
 {
  //solve "(expression)"
  case '(':
  { 
   double d = expression();
   t = ts.get();
   checkForChar(t, ')');
   return d;
  }

  //solve "-primary"
  case '-':
   return -primary();

  //solve "number"
  case number:
   return t.value;

  //solve "let name = value"
  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();
   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();

   //get next char after 'expression' if not ',' then error   
   t = ts.get();
   checkForChar(t, ',');

   //get the expression after ','
   double i = expression();

   //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");
 }
}

//read in values that would normally come after a primary
double term()
{
 double left = primary();
 while (true) 
 {
  Token t = ts.get();
  switch (t.kind) 
  {
   case '*':
    left *= primary();
    break;
   case '/':
   { 
    double d = primary();
    if (d == 0) 
     error("divide by zero");
    left /= d;
    break;
   }
   case '%':
   {
    double d = primary();
    if (d == 0)
     error("%:divide by zero");
    left = fmod(left, d);
    break;
   }
   default:
    ts.unget(t);
    return left;
  }
 }
}

//can be used before a primary
double expression()
{
 double left = term();
 while (true) 
 {
  Token t = ts.get();

  switch (t.kind) 
  {
   case '+':
    left += term();
    break;
   case '-':
    left -= term();
    break;
   default:
    ts.unget(t);
    return left;
  }
 }
}

double statement()
{
 Token t = ts.get();
 switch (t.kind) 
 {
  case let:
   return symbols.declare();
  default:
   ts.unget(t);
   return expression();
 }
}

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()
{
 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() << 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);

 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;
}

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' after 'let'\n";
 cout << "4. To find the squareroot, type 'sqrt(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