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 { vectorvar_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