Pages

Wednesday, 28 March 2018

Chapter 8 // Drill 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 // Drill 1



1. Create three files: my.h, my.cpp, and use.cpp. The header file my.h contains:

extern int foo;
void print_foo();
void print(int);

The source code file, my.cpp #includes my.h and std_lib_facilities.h, defines print_foo() to print the value of foo using cout, and print(int i) to print the value of i using cout.

The source code file use.cpp #includes my.h, defines main() to set the value of foo to 7 and print it using print_foo(), and to print the value of 99 using print(). Note that use.cpp does not #include std_lib_facilities.h as it doesn't directly use any those facilities.

Get these files compiled and run. On Windows, you need to have both use.cpp and my.cpp in a project and use { char cc; cin >> cc} in use.cpp to be able to see your output. Hint: You need to #include <iostream> to use cin.


//my.h
#pragma once
//my.h

//declarations
extern int foo;
void print_foo();
void print(int);

void keepWindowOpen();

//my.cpp

//my.cpp

#include "my.h"
#include "std_lib_facilities.h"

int foo;

//print the value of foo
void print_foo()
{
 cout << foo << endl;
}

//print a given integer
void print(int i)
{
 cout << i << endl;
}

//function to keep the window open
void keepWindowOpen()
{
 char c;
 cout << "\nPress any key to quit: ";
 cin >> c;
}


//use.cpp

//use.cpp

#include "my.h"

int main()
{
 //assign 7 to foo
 foo = 7;

 //print what's in foo
 print_foo();

 //send an integer to be printed to screen
 print(99);

 //keep window open until any key press
 keepWindowOpen();
}


A pretty simple exercise which showcases how to split a project up into separate files. Seeing as how you can't use keep_window_open() from std_lib_facilities I created a function in my.cpp which does the same thing and then declared it in my.h so it could be used in main(). This way I didn't have to #include <iostream> in use.cpp as it was already declared in my.cpp from the other header file.

Sunday, 25 March 2018

Chapter 7 // Exercise 7, 8, 9, 10, 11 - 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 7


7. Change the q and h commands to be quit and help respectively.


I'm not quite sure what he means by this one. Does he want us to change what to check for i.e have the user type 'quit' and 'help' to quit and display help? Drill 10 told us to change the quit keyword to exit and define a string for quit. I've been using 'quit' to exit the program since just because I prefer it. If so, that simply involves defining another const string called helpKey and assigning "help". Then in ts.get() checking to see if help was typed in and returning Token(help) if so.

 Chapter 7 // Exercise 8

8. The grammar in section 7.6.4 is incomplete; it does not define sequences of statements, such as 4+4; 5-6; and it does not incorporate the grammar changes outlined in section 7.8. Fix the grammar. 

I really don't like these exercises. This 'grammar' stuff confuses the hell out of me. But I'll give it a go:

Calculation:
    Statement
    Print
    Help
    Quit
    Calculation Statement

Statement:
    Declaration
    Expression

Declaration:
    "let" Name "=" Expression
    "let "const" Name "=" Expression

Expression:
    Term
    Expression + Term
    Expression - Term

Term:
    Primary
    Term * Primary
    Term / Primary
    Term % Primary

Primary:
    Number
    "(" Expression ")"
    "-" Primary
    "+" Primary
    Expression "=" Name
    "sqrt(" Expression ")"
    "pow(" Expression "," Expression ")"

Number:
    Floating-point literal

 Chapter 7 // Exercise 9

9. Suggest three improvements (not mentioned in this chapter) to the calculator. Implement one of them.

Over the course of the exercises I've already implemented three extra things. The first was to allow users to re-assign a keyword if they wanted to so long as they hadn't set it to const. The second was to create a reusable function that could check for a given character. And in the third I changed the declare() function to be a part of the Symbols class.


 Chapter 7 // Exercise 10

10. Modify the calculator to operate on ints (only): give errors for overflow and underflow. Hint: use narrow_cast (section 7.5).

I had a look around the internet and the general consensus is that you cannot specifically check for overflow or underflow, then again I often misinterpret what is being asked for. I did however find a useful answer here that explained how narrow_cast is defined by Bjarne himself in his more advanced book. It would appear that narrow_cast gives an error itself when data is lost. Knowing this, I then changed all the double values to ints, making sure that where appropriate they were always assigned using narrow_cast to give the error warning. That said, Visual Studio will tell you where you need to use it as I found a couple of times as it won't compile when there is a possible loss of data.

 Chapter 7 // Exercise 11

11. Revisit two programs you wrote for the exercises in Chapter 4 or 5. Clean up up that code according to the rules outlined in this chapter. See if you find any bugs in the process.


For this exercise I chose Exercise 10 from Chapter 4 and Exercise 14 in Chapter 5. These can be found here and here in the original posts for these exercises.

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

}

Sunday, 11 March 2018

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


5. Modify Token_stream::get() to return Token(print) when it sees a new line. This implies looking for whitespace characters and treating newline ('\n') specifically.You might find the standard library function isspace(ch), which returns true is ch is a whitespace character useful.


This stumped me a little until I did some research and found that cin >> skips whitespace. I know it was already in the comments but my brain clearly wasn't working properly. A quick search showed that cin.get() doesn't skip whitespace allowing you to check for special characters such as '\n'.

I simply changed the char print to '\n' and then added a while loop in get() which checks to see if there is any whitespace in the buffer. If there is it checks to see if that whitespace is a newline, if it is it returns the command to print, if not it gets the next char in the buffer as we still need to check for spaces inbetween naming.


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

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

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


Sunday, 4 March 2018

Chapter 7 // Exercise 1, 2, 3 - 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 1


1. Allow underscores in the calculator's variable names.


This suffers from the same problem as the drill 10 in chapter 7 in that the default checks to see if it is a letter, if it isn't and the character doesn't match any of the other cases then it will simply return "bad token".

However there is a simple way round this by just adding a check for '_' onto the if statements as a logical 'or':


   default:

   //do this if ch is a letter or underscore

   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 == 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 parameters return an error message

   error("Bad token");

Chapter 7 // Exercise 2

2. Provide an assignment operator, =, so that you can change the value of a variable after you introduce it using let. Discuss why that can be useful and how it can be a source of problems.

There are many different and ways to implement this. The simplest is to comment out the if statement that checks to see if there is already a variable with the same name. Then you can redefine the same variable as many times as you like. However, I also decided to implement a way to inform the user that the variable was already in use and ask them if they would like to reassign it. This involved flushing out the stream first, then firing off a few if statements. There is probably a quicker way to do this but I seem to just like doing things by brute force.

 
 if (is_declared(name))
 {
  cout << name + ", declared twice. Would you like to reassign? 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 << "Please enter new value: ";
   int val;
   cin >> val;
   set_value(name, val);
   double d = val; //return value to print to reset calculator
   return d;
  }
 }


If the user does select "y" to reassign, they are prompted to enter their new value. set_value() is then called to set the new value instead of pushing back a new variable into the vector. The value is then returned to 'reset' the calculator of sorts.

It's a good idea because someone may have made a mistake when entering the value for a new variable, however it is mainly a bad idea as it can cause more harm than good; especially if a program saves values. I would stick to only allowing a variable to be assigned upon initialisation as it's easy to just create a new variable with a new value.


Chapter 7 // Exercise 3

3. Provide named constants that you really can't change the value of. Hint: You have to add a member to Variable that distinguishes between constants and variables and check for it in set_value(). If you want to let users define constants, you'll have to add a notation to let the user express that , for example, const pi = 3.14

First I added a new const char and const string to the top of the program:
 
const char isConst = 'C';
const string constKey = "const";

Then a new return had to be added for constkey in token::get():
 
if(s == constKey)
return Token(isConst);

The struct Variable was then modified to accept a bool value to check if the variable has been define as Const or not:
 
struct Variable 
{
 string name;
 double value;
 bool isConst;
 Variable(string n, double v, bool ic) :name(n), value(v), isConst(ic) { }
};

Set_Value() was then changed to check if the variable being set was not a const.
 
//set the Variable named s to d
void set_value(string s, double d)
{
 for (int i = 0; i <= names.size(); ++i)
 {
  //allow redefinitions as long as variable isn't const
  if (names[i].name == s && names[i].isConst == false) 
  {
   names[i].value = d;
   return;
  }
 }

 error("set: undefined name ", s);
}
is_declared() and define_name() were also modified to make sure that the already-defined variable trying to be redefined is not a const variable:
 
//is variable already declared?
bool is_declared(string s)
{
 for (int i = 0; i < names.size(); ++i)
 {
  if (names[i].name == s && names[i].isConst == true)
   error("Cannot reassign const variable");
  else if (names[i].name == s && names[i].isConst == false)
   return true;
 }

 return false;
}

//allow programmers to add (var,val) to variable vector
double define_name(string var, double val, bool isConst)
{
 if (is_declared(var))
  error(var, " declared twice");

 names.push_back(Variable(var, val, isConst));

 return val;
}

Last but not least was the modification to declaration, which gets the next word in the stream. It checks for 'Const' being next, if its not then the Variable being defined is not const.
 
//check for name definintion errors
double declaration()
{
 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 (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;
   set_value(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(); names.push_back(Variable(name, d, isC)); return d; }