Pages

Wednesday, 7 March 2018

Chapter 7 // Exercise 4 - 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:




Chapter 7 // Exercise 4


4. The get_value(), set_value(), is_declared(), and define_name() functions all operate on the variable var_table. Define a class called Symbol_table with a member var_table of type vector<Variable> and member functions get(), set(), is_declared() and declare(). Rewrite the calculator to use a variable of type Symbol_table.


At first I wasn't quite sure what he meant, especially when he said "rewrite the calculator", then I realised he simply wanted us to make the already used get_value(), set_value(), is_declared() and define_name() to be part of a class instead of just floating around. I decided to add declaration() to the class as well, which is now symbols.declare(). This meant I had to forward declare expression() and Token ts but the code feels a little cleaner now and less all over the place.


// pandp.cpp : Defines the entry point for the console application.
//
#include "stdafx.h"
#include "std_lib_facilities.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();

const char let = 'L';
const char quit = 'Q';
const char print = ';';
const char number = '8';
const char name = 'a';
const char squareR = 's';
const char findPow = 'p';
const char isConst = 'C';

const string declKey = "let";
const string quitKey = "quit";
const string sqrtKey = "sqrt";
const string powKey = "pow";
const string constKey = "const";

//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 >> ch;  //skips whitespace
 switch (ch) 
 {
  case '(': 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
  }

  //allo user defined variables if user types #
  case '#':
   return Token(let);
  
  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 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; 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 definintion errors
double Symbol_table::declare()
{
 Token t = ts.get();

 //is const the next word in the stream?
 bool isC;
 if (t.kind == 'C')
 {
  isC = true;
  t = ts.get();  //get the the next word in the stream for the name
 }
 else
  isC = false;

 if (t.kind != 'a')
  error("name expected in declaration;");

 string name = t.name;
 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()
{
 ts.ignore(print);
}

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


No comments:

Post a Comment