There's another calculator example code I need to debug for an assignment in the book. I've already taken care of the full-on compiler errors, there are just some logic-errors and other runtime errors left now, probably. Plus there are some Drill specifications I need help with.
Here's the code:
Code:
#include <iostream>
#include <cctype>
#include <vector>
#include <stdexcept>
#include <cmath>
#include "../custom_std_lib_facilities.h"
using namespace std;
double get_value(string s);
void set_value(string s, double d);
bool is_declared(string s);
double expression();
double primary();
double term();
double expression();
void calculate();
void clean_up_mess();
double declaration();
double statement();
// user-defined class for characters entered for calculation or other commands while running the program
struct Token {
char kind;
double value;
string name;
Token(char ch) :kind{ch} {}
Token(char ch, double val) :kind{ch}, value{val} {}
Token(char ch, string n) :kind{ch}, name{n} {}
};
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);
};
int main()
try {
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;
}
const char number = '8'; // t.kind == number means that t is a number Token
const char quit = 'q'; // t.kind == quit means that t is a quit Token
const char print = ';'; // t.kind == print means that t is a print Token
const char name = 'a';
const char let = 'L';
const string declkey = "let";
Token Token_stream::get()
{
if (full) { full=false; return buffer; }
char ch;
cin >> ch;
switch (ch) {
case '(':
case ')':
case '+':
case '-':
case '*':
case '/':
case '%':
case ';':
case '=':
return Token(ch);
case '.':
case '0':
case '1':
case '2':
case '3':
case '4':
case '5':
case '6':
case '7':
case '8':
case '9':
{ cin.putback(ch);
double val;
cin >> val;
return Token(number,val);
}
default:
if (isalpha(ch)) {
string s;
s += ch;
while(cin.get(ch) && (isalpha(ch) || isdigit(ch))) s=ch;
cin.unget();
if (s == "let") return Token(let);
if (s == "quit") return Token(name);
return Token(name,s);
}
error("Bad token");
}
}
void Token_stream::ignore(char c)
{
if (full && c==buffer.kind) {
full = false;
return;
}
full = false;
char ch;
while (cin>>ch)
if (ch==c) return;
}
class Variable {
public:
string name;
double value;
Variable(string n, double v) :name(n), value(v) { }
};
//vectors for declared names and variables
vector<Variable> names;
vector<Variable> var_table;
double get_value(string s)
{
for (unsigned i = 0; i<names.size(); ++i)
if (names[i].name == s) return names[i].value;
error("get: undefined name ",s);
}
void set_value(string s, double d)
{
for (unsigned i = 0; i<=names.size(); ++i)
if (names[i].name == s) {
names[i].value = d;
return;
}
error("set: undefined name ",s);
}
bool is_declared(string s)
{
for (unsigned i = 0; i<names.size(); ++i)
if (names[i].name == s) return true;
return false;
}
Token_stream ts;
double primary()
{
Token t = ts.get();
switch (t.kind) {
case '(':
{
double d = expression();
t = ts.get();
if (t.kind != ')') error("'(' expected");
return d;
}
case '-':
return - primary();
case number:
return t.value;
case name:
return get_value(t.name);
default:
error("primary expected");
}
}
double term()
{
double left = primary();
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("division by 0");
}
left = fmod(left, d);
t = ts.get();
return left;
}
default:
ts.unget(t);
return left;
}
}
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 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(cin)
try
{
std::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);
std::cout << result << statement() << '\n';
}
catch(runtime_error& e) {
cerr << e.what() << endl;
clean_up_mess();
}
}
As for the Drills specs, the ones I need help on are:
2. Go through the entire program and add appropriate comments.
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.
4. Testing: prepare a set of inputs and use them to test the calculator. Is your list pretty complete? What should you look for? Include negative values, 0, very small, very large, and “silly” inputs.
5. Do the testing and fix any bugs that you missed when you commented.
6. Add a predefined name k meaning 1000.
7. Give the user a square root function sqrt(), for example, sqrt(2+6.7). Naturally, the value of sqrt(x) is the square root of x; for example, sqrt(9) is 3. Use the standard library sqrt() function that is available through the header std_lib_facilities.h. Remember to update the comments, including the grammar.
8. Catch attempts to take the square root of a negative number and print an appropriate error message.
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 %.
10. Change the “declaration keyword” from let to #.
11. Change the “quit keyword” from quit to exit. That will involve defining a string for quit just as we did for let in §7.8.2.
There are also some Exercises for this chapter and the previous one that I need help with, but I'll ask for that later.
Right now, I need to put two semi-colons to end a statement in the console window when doing a calculation (e.g. 45/5; when I run the calculator code. I also need to know how to fix that. And the 'q' is supposed to terminate the loop and quit the program, but it isn't working, so I have to use Ctrl+C to quit.
Also, I could use some suggestions for an implementation of member function Variable::Variable() in this other calculator code I'm working on for the book:
Code:
//
// This is example code from Chapter 6.6 "Trying the first version" of
// "Software - Principles and Practice using C++" by Bjarne Stroustrup
// Edited and run by Osman Zakir
// 4/26/2015
//
/*
Simple calculator
Revision history:
Revised by Bjarne Stroustrup November 2013
Revised by Bjarne Stroustrup May 2007
Revised by Bjarne Stroustrup August 2006
Revised by Bjarne Stroustrup August 2004
Originally written by Bjarne Stroustrup
([email protected]) Spring 2004.
This program implements a basic expression calculator.
Input from cin; output to cout.
The grammar for input is:
Statement:
Expression
Print
Quit
Print:
;
Quit:
q
Expression:
Term
Expression + Term
Expression – Term
Term:
Primary
Term * Primary
Term / Primary
Term % Primary
Primary:
Number
( Expression )
– Primary
+ Primary
Number:
floating-point-literal
Input comes from cin through the Token_stream called ts.
*/
#include <iostream>
#include <stdexcept>
#include <cmath>
#include <vector>
#include <cctype>
#include "../custom_std_lib_facilities.h"
class Token {
public:
char kind;
double value;
std::string name;
Token(char ch) :kind{ch} {}
Token(char ch, double val) :kind{ch}, value{val} {}
Token(char ch, std::string n) :kind{ch}, name{n} {}
};
class Token_stream {
public:
Token_stream();
Token get(); // get a Token
void putback(Token t); // put a Token back
void ignore(char c); // discard characters up to and including a c
private:
bool full {false};
Token buffer;
};
class Variable {
public:
std::string name;
double value;
Variable(std::string var, double val);
};
const char number = '8'; // t.kind == number means that t is a number Token
const char quit = 'q'; // t.kind == quit means that t is a quit Token
const char print = ';'; // t.kind == print means that t is a print Token
const std::string prompt = "> ";
const std::string result = "= "; // used to indicate that what follows is a result
std::vector<Variable> var_table;
const char name = 'a';
const char let = 'L';
const std::string declkey = "let";
Token_stream ts;
void calculate();
double expression(); // read and evaluate a Expression
void clean_up_mess();
double term(); // read and evaluate a Term
void keep_window_open();
void keep_window_open(std::string s);
void error(const std::string& s);
double get_value(std::string s);
void set_value(std::string s, double d);
double statement();
bool is_declared(std::string var);
double define_name(std::string var, double val);
double declaration();
int main()
{
try
{
// predefined names:
define_name("pi", 3.1415926535);
define_name("e", 2.7182818284);
std::cout << "Welcome to our simple calculator. We can handle the operations '+', '-', '*', '/', and '%' (for remainders).\n";
std::cout << "Negative numbers are allowed, but please also include '+' at the front of a positive number (e.g +45+5=)\n.";
std::cout << "To get the result for an expression, press ';'.\n";
std::cout << "To stop enter values and to reach the end of the program, press 'q'. Good luck.\n";
std::cout << "The '>' on the screen means you can do a calculation.\n\n";
calculate();
keep_window_open(); // cope with Windows console mode
return 0;
}
catch (std::exception& e)
{
std::cerr << e.what() << std::endl;
keep_window_open ("~~");
return 1;
}
catch (...)
{
std::cerr << "exception \n";
keep_window_open ("~~");
return 2;
}
}
void calculate()
{
while (std::cin)
try
{
std::cout << prompt;
Token t = ts.get();
while (t.kind == print)
{
t = ts.get(); // first discard all "prints"
}
if (t.kind == quit)
{
return;
}
ts.putback(t);
std::cout << result << statement() << '\n';
}
catch (std::exception& e)
{
std::cerr << e.what() << '\n'; // write error message
clean_up_mess();
}
}
void clean_up_mess()
{
ts.ignore(print);
}
Token_stream::Token_stream()
:full(false), buffer(0) // no Token in buffer
{
}
void Token_stream::putback(Token t) // put a Token back into the Token stream
{
buffer = t;
full = true;
}
Token Token_stream::get() // read from cin and compose a Token
{
if (full)
{
full = false;
return buffer;
}
char ch;
std::cin >> ch;
switch (ch)
{
case quit:
case print:
case '(':
case ')':
case '{':
case '}':
case '+':
case '-':
case '*':
case '/':
case '%':
return Token{ch}; // let each character represent itself
case '.': // a floating-point literal cans start with a dot
case '0': case '1': case '2': case '3': case '4':
case '5': case '6': case '7': case '8': case '9': // numeric literal
{
std::cin.putback(ch); // put digit back into the input stream
double val;
std::cin >> val;
return {number, val};
}
default:
if (isalpha(ch))
{
std::cin.putback(ch);
std::string s;
s += ch;
while (std::cin.get(ch) && (isalpha(ch) || isdigit(ch)))
{
s += ch;
}
std::cin.putback(ch);
if (s == declkey)
{
return Token{let};
}
return Token{name, s};
}
error("Bad token");
}
}
void Token_stream::ignore(char c)
{
if (full && c == buffer.kind)
{
full = false;
return;
}
full = false;
// now search input
char ch = 0;
while (std::cin >> ch)
{
if (ch == c)
{
return;
}
}
}
double primary() // read and evaluate a Primary
{
Token t = ts.get();
switch (t.kind) {
case '(': // handle '(' expression ')'
{
double d = expression();
t = ts.get();
if (t.kind != ')') error("')' expected");
return d;
}
case number:
return t.value;
case '-':
return - primary();
case '+':
return primary();
default:
error("primary expected");
}
}
double expression()
{
double left = term();
Token t = ts.get();
while (true) {
switch (t.kind)
{
case '+':
left += term();
t = ts.get();
break;
case '-':
left -= term();
t = ts.get();
break;
default:
ts.putback(t);
return left;
}
}
}
double statement()
{
Token t = ts.get();
switch (t.kind)
{
case let:
return declaration();
default:
ts.putback(t);
return expression();
}
}
double declaration()
// assume we have seen "let"
// handle: name = expression
// declare a variable called "name" with the initial value "expression"
{
Token t = ts.get();
if (t.kind != name)
{
error("name expected in declaration");
}
std::string var_name = t.name;
Token t2 = ts.get();
if (t2.kind != '=')
{
error("= missing in declaration of ", var_name);
}
double d = expression();
define_name(var_name, d);
return d;
}
double term()
{
double left = primary();
Token t = ts.get();
while (true) {
switch (t.kind)
{
case '*':
left *= primary();
t = ts.get();
break;
case '/':
{
double d = primary();
if (d == 0)
{
error("divide by 0");
}
left /= d;
t = ts.get();
break;
}
case '%':
{
double d = primary();
if (d == 0)
{
error("division by 0");
}
left = fmod(left, d);
t = ts.get();
break;
}
default:
ts.putback(t);
return left;
}
}
}
void set_value(std::string s, double d)
// set the Variable named s to d
{
for (Variable& v : var_table)
{
if (v.name == s)
{
v.value = d;
return;
}
}
error("set: undefined variable ", s);
}
bool is_declared(std::string var)
// is var already in var_table?
{
for (const Variable& v : var_table)
{
if (v.name == var)
{
return true;
}
return false;
}
}
double define_name(std::string var, double val)
// add (var, val) to var_table
{
if (is_declared(var))
{
error(var, " declared twice");
}
var_table.push_back(Variable(var, val));
return val;
}
double get_value(std::string s)
// return the value of a Variable named s
{
for (const Variable& v : var_table)
{
if (v.name == s)
{
return v.value;
}
error("get: undefined variable ", s);
}
}
This is the same one I initially asked about in this thread, hence the exact same comments above the "#include" directives.