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