Sunday, 25 February 2018

Chapter 7 // Drill 1, 2, 3, 4, 5, 6, 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 // Drill 1


1. Starting from the file calculator08buggy.cpp, get the calculator to compile.


The program will not compile due to 1 error in Token Token_stream::get() near the end where "return Token(name, s);". The error given says "no instance of Token::Token matches argument list". This is because the two functions in struct Token only except a char or a char and a double. Line changed to "Token(name)"

In the actual file itself it says there are 3 errors that the compiler will catch however, mine only caught 1 that needed fixing before it could compile.

Chapter 7 // Drill 2

2. Go through the entire program and add appropriate comments.

These will be seen below with the entire code.

Chapter 7 // Drill 3


3. As you commented, you found errors (deviously inserted especially for you to find). Fix them; they are not in the text of the book.

  1. If s == 'quit' then token should return quit, not name.
  2. In primary, if '(' is used without ')' the error should return "expected ')'" 
  3. In primary, case '(' was missing a return or break. d should be returned.
  4. Modulo % was given as a case however there is no code to define what to do with it.

Chapter 7 // Drill 4, 5

4. Testing: Prepare a set of inputs and use them to test the calculator
5. Do the testing ad fix any bugs that you missed when you commented

I'm not going to show the testing report however I did find one bug. The 'let' case is inputting everything not already defined as the exact same thing. For example if you put "let k = 1000" then would r = 1000 and j = 1000 and so on. I realised this was because get() was not returning the string s and applying it to name; it was returning an empty string because I had changed the return to Token(name) in drill 1 to get it to compile. Turns out the Token function which returns a char and string was missing in the struct so that was added.

I'm sure there are more however I have limited time to do these exercises now so I spent as much time as I could combing through the code. I feel I fixed most of them when commenting anyway.

Chapter 7 // Drill 6

6. Add a predefined name k meaning 1000

The define_name() function had to be added in for this to work, it can be found on page 246 at the top. Then we can define as many names as we want in main().

Chapter 7 // Drill 7, 8

7. Give the user a square root function: sqrt(). Use the standard library. Remember to update comments including grammar.
8. Catch attempts to take square root of a negative number and print and appropriate error message.

I added the following to the get() function in the default section:

if (s == "sqrt")
     return Token(squareR);

squareR is const char defined earlier with the other const chars, I set the char to 's'.

Then in primary() the following case was added to solve for "sqrt()":


//solve "sqrt(expression)"
case squareR:
{
//get next char after 'sqrt' if not '(' then error
t = ts.get();
if (t.kind != '(')
error("'(' expected");

  //if expression is less than 0 print an error
double d = expression();
if (d < 0)
error("Cannot square root negative integers");

  //get next char after expression, if not ')' then error
t = ts.get();
if (t.kind != ')')
error("')' expected");

  //return square root of the expression taken from the tokenstream
return sqrt(d);

}

I originally tried to define sqrt as a variable however changing the value depending on what was inputted was a little to cumbersome. We already solve for parenthesis so I added another case under "let" and "quit" in get() to tell the program to do something else if the user types in "sqrt". Then in that case it is a simple as checking if the next character in the stream is '(', if it's not; error. The case then solves for an expression or whatever may be between the brackets, just as in the case for '('. We give an error for negative numbers and another if ')' is not provided. The case then returns the square root of the number found by using the sqrt() function found in <cmath>.


Chapter 7 // Drill 9

9. Allow the user to use pow(x, i) to mean "Multiply x with itself i times": for example, pow(2.5,3) is 2.5*2.5*2.5. Require i to be an integer using the technique we used for %.

This is simply modifying the case for square root. I added another const char called findPow and assigned it 'p'. Then in get() I added another case to solve for ',' and a couple lines underneath squareRoot:

if (s == "pow")

     return Token(findPow);

Then in primary the following case was added to solve for "pow(,)":

//solve "pow(expression, expression)"
case findPow:
{
//get next char after 'pow' if not '(' then error 
t = ts.get();
if (t.kind != '(')
error("'(' expected");

//get the expression after '('
double d = expression();

//get next char after 'expression' if not ',' then error 
t = ts.get();
if (t.kind != ',')
error("',' expected");

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

//get next char after expression, if not ')' then error
t = ts.get();
if (t.kind != ')')
error("')' expected");

// return expression using pow() from <cmath>
return pow(d, i);

}

It is a bit hacky, I don't like the repeated getting of '('; I may turn that into a function for the final version which I will be putting at the bottom of the post. As you can see though it is very similar to solving for 'sqrt' as it's just a case of telling the program to get another character and see what it is. 



Chapter 7 // Drill 10

10. Change the "declaration keyword" from let to #.

By doing this there is a problem in that # is not a letter so it will not get to the default section of get() and the compiler will return "bad token" if # is not solved for correctly. To get round this simply create another case in get():

case '#':
                  return Token(let);

I left the string version in the default section though just in case we ever want to go back to using "let" instead of "#". But it won't interfere with the code so it's OK to leave to it there.


Chapter 7 // Drill 11

11. Change the "quit keyword" from quit to exit. That will involve defining a string for quit just as we did for let.

For this we can simply create a few const strings underneath where we define the const chars like so:

const string declKey = "let";      //unused as of drill 10
const string quitKey = "quit";
const string sqrtKey = "sqrt";

const string powKey = "pow";

Then we can adjust the code in get() to use these strings instead of actual strings meaning this is the only place we have to change in the code now if we decide to change the meaning of any other keywords at anytime.


Finished Code

Here is my complete finished code from completing all the drills in chapter 7:

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

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 string declKey = "#";
const string quitKey = "quit";
const string sqrtKey = "sqrt";
const string powKey = "pow";

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

//allow user defined variables if user types #
case '#':
return Token(let);
default:
//do this if ch is a letter
if (isalpha(ch)) 
{
string s;
s += ch;

//while there are still chars in cin, read them into s
while (cin.get(ch) && (isalpha(ch) || isdigit(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");
}
}

//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;
Variable(string n, double v) :name(n), value(v) { }
};

vector<Variable> names;

//return the value of the Variable named s
double get_value(string s, double val)
{
for (int i = 0; i < names.size(); ++i)
{
if (names[i].name == s)
{
return names[i].value;
}
}

error("get: undefined name ", s);
}

//set the Variable named s to d
void set_value(string s, double d)
{
for (int i = 0; i <= names.size(); ++i)
if (names[i].name == s) 
{
names[i].value = d;
return;
}

error("set: undefined name ", s);
}

//is variable already declared?
bool is_declared(string s)
{
for (int i = 0; i < names.size(); ++i)
{
if (names[i].name == s) 
return true;
}

return false;
}

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

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

return val;
}

Token_stream ts;

double expression(); //forward declaration

//check tokenstream for 'your char here'
Token checkForChar(Token t, char ch)
{
  //convert ch to string for error message string chstring = ""; chstring += ch; error("'" + chstring + "' expected");
}

//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 get_value(t.name, 0);

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

//check for name definition errors
double declaration()
{
Token t = ts.get();
if (t.kind != 'a') 
error("name expected in declaration");

string name = t.name;
if (is_declared(name)) 
error(name, " declared twice");

Token t2 = ts.get();
if (t2.kind != '=') 
error("= missing in declaration of ", name);

double d = expression();
names.push_back(Variable(name, d));

return d;
}


double statement()
{
Token t = ts.get();
switch (t.kind) 
{
case let:
return declaration();
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 {
define_name("pi", 3.1415926535);
define_name("e", 2.7182818284);
define_name("k", 1000);

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;

}