Thread: Calculator Programming using Tokens not compiling

  1. #1
    Registered User
    Join Date
    Mar 2015
    Posts
    384

    Calculator Programming using Tokens not compiling

    There's a calculator program I need to test-run, but it won't compile, probably because of some sort of syntax error. The program is using Stroustrup's custom library, std_lib_facilities.h, which I've actually saved with the files that came with my Compiler when I downloaded it, so I can now include it with the
    Code:
    #include <std_lib_facilities.h>
    syntax rather than the double quotes one. You can read the header-file online here, if you need to.

    Here is my code (was originally example source code from the book's resource website that I was directed to edit to make it work as per instructions from the book, but I seem to have done something wrong):
    Code:
    //
    // This is example code from Chapter 6.6 "Trying the first version" of
    // "Software - Principles and Practice using C++" by Bjarne Stroustrup
    //
    
    #include <std_lib_facilities.h>
    
    //------------------------------------------------------------------------------
    
    class Token {
    public:
        char kind;        // what kind of token
        double value;     // for numbers: a value
        Token(char ch)    // make a Token from a char
            :kind(ch), value(0) { }
        Token(char ch, double val)     // make a Token from a char and a double
            :kind(ch), value(val) { }
    };
    
    class Token_stream {
    public:
        Token_stream();     // make a Token stream that reads from cin
        Token get();        // get a Token
        void putback(Token t);     // put a Token back
    private:
        bool full {false};      // is there a Token in the buffer?
        Token buffer;       // here is where we keep a Token put back using putback()
    };
    
    double expression();  // read and evaluate a Expression
    
    //------------------------------------------------------------------------------
    
    double term();        // read and evaluate a Term
    
    //------------------------------------------------------------------------------
    
    int main()
    {
        try
        {
            while (cin)
            {
                cout << expression() << '\n';
                keep_window_open("~0");
            }
        }
        catch (exception& e)
        {
            cerr << e.what() << endl;
            keep_window_open ("~1");
            return 1;
        }
        catch (...)
        {
            cerr << "exception \n";
            keep_window_open ("~2");
            return 2;
        }
        double val = 0;
        while (cin)
        {
            Token_stream ts;
            Token t = ts.get();
            if (t.kind == 'q')      // 'q' for "quit"
            {
                break;
            }
            if (t.kind == ';')
            {
                cout << "=" << val << '\n';
            }
            else
            {
                ts.putback(t);
            }
            val = expression();
        }
    }
    
    void Token_stream::putback(Token t)
    {
        buffer = t;     // copy t to buffer
        full = true;    // buffer is now full
    }
    
    Token Token_stream::get()
    {
        if (full)       // do we already have a Token ready?
        {
            full = false;      // remove Token from buffer
            return buffer;
        }
        char ch;
        cin >> ch;      // note that >> skips whitespace (space, newline, tab, etc.)
        switch (ch)
        {
        case ';':       // for "print:
        case 'q':       // for "quite"
        case '(': case ')': case '+': case '-': case '*': case '/':
            return Token{ch};       // let each character represent itself
        case '.':
        case '0': case '1': case '2': case '3': case '4':
        case '5': case '6': case '7': case '8': case '9':
            {
                cin.putback(ch);       // put digit back into the input stream
                double val;
                cin >> val;     // read a floating-point number
                return {'8', val};      // let '8' represent "a number"
            }
        default:
            error("Bad token");
        }
    }
    
    double primary()     // read and evaluate a Primary
    {
        Token_stream ts;
        Token t = ts.get();
        switch (t.kind) {
        case '(':    // handle '(' expression ')'
            {
                double d = expression();
                t = ts.get();
                if (t.kind != ')') error("')' expected");
                return d;
            }
        case '8':            // we use '8' to represent a number
            return t.value;  // return the number's value
        default:
            error("primary expected");
        }
    }
    //------------------------------------------------------------------------------
    
    double expression()
    {
        double left = term();       // read and evaluate a term
        Token_stream ts;
        Token t = ts.get();         // get the next Token from the Token stream
        while (true) {
            switch (t.kind)
            {
            case '+':
                left += term();         // evaluate term and add
                ts.get();
                break;
            case '-':
                left -= term();         // evaluate term and subtract
                t = ts.get();
                break;
            default:
                ts.putback(t);          // put t back into the Token stream
                return left;            // finally: no more + or -; return the answer
            }
        }
    }
    
    //------------------------------------------------------------------------------
    
    double term()
    {
        double left = primary();
        Token_stream ts;
        Token t = ts.get();         // get the next Token from the Token stream
        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;
                }
            default:
                ts.putback(t);          // put t back into the Token stream
                return left;
            }
        }
    }
    These are the error messages it's giving me when I try to compile it:
    ||=== Build: Debug in calculator (compiler: GNU GCC Compiler) ===|
    C:\Users\Osman\programming\stroustrup_programming_ using_c++\calculator\calculator.cpp||In member function 'Token Token_stream::get()':|
    C:\Users\Osman\programming\stroustrup_programming_ using_c++\calculator\calculator.cpp|114|warning: control reaches end of non-void function [-Wreturn-type]|
    C:\Users\Osman\programming\stroustrup_programming_ using_c++\calculator\calculator.cpp||In function 'double primary()':|
    C:\Users\Osman\programming\stroustrup_programming_ using_c++\calculator\calculator.cpp|133|warning: control reaches end of non-void function [-Wreturn-type]|
    obj\Debug\calculator.o||In function `main':|
    C:\Users\Osman\programming\stroustrup_programming_ using_c++\calculator\calculator.cpp|63|undefined reference to `Token_stream::Token_stream()'|
    obj\Debug\calculator.o||In function `primary()':|
    C:\Users\Osman\programming\stroustrup_programming_ using_c++\calculator\calculator.cpp|118|undefined reference to `Token_stream::Token_stream()'|
    obj\Debug\calculator.o||In function `expression()':|
    C:\Users\Osman\programming\stroustrup_programming_ using_c++\calculator\calculator.cpp|139|undefined reference to `Token_stream::Token_stream()'|
    obj\Debug\calculator.o||In function `term()':|
    C:\Users\Osman\programming\stroustrup_programming_ using_c++\calculator\calculator.cpp|164|undefined reference to `Token_stream::Token_stream()'|
    ||=== Build failed: 4 error(s), 2 warning(s) (0 minute(s), 1 second(s)) ===|
    Of course, not all of them are errors; two of them are warnings.

    Alright, please help me out here. I really need to get this programming running.

  2. #2
    Guest
    Guest
    Code:
    114|warning: control reaches end of non-void function [-Wreturn-type]|
    These warnings occur because there's a scenario where you return nothing (despite the method having a return type). This happens if your switch statement falls through to default, for instance.

    Code:
    63|undefined reference to `Token_stream::Token_stream()'|
    You use the Token_stream class, but it looks like you haven't defined any constructor (Token_stream::Token_stream()).

  3. #3
    Registered User
    Join Date
    May 2010
    Posts
    120
    probably because of some sort of syntax error
    Did you even read the output? I feel you took no effort to at least try to find the problem online. "Undefined references" are a common error.

    Also, for my amusement, here's a list of redundant and needless comments in your code:

    Code:
    class Token {
    public:
        char kind;        // what kind of token
        double value;     // for numbers: a value
        Token(char ch)    // make a Token from a char
            :kind(ch), value(0) { }
        Token(char ch, double val)     // make a Token from a char and a double
            :kind(ch), value(val) { }
    };
    This is not how you do comments. "what kind of token?" Really? What else would it be? What kind of cat? Instead this should do something like explain what kinds of tokens are allowed. This whole thing would be self documenting if you just used enum classes. "for numbers: a value" What? And the other two should be really self explanatory.

    Code:
    class Token_stream {
    public:
        Token_stream();     // make a Token stream that reads from cin
        Token get();        // get a Token
        void putback(Token t);     // put a Token back
    private:
        bool full {false};      // is there a Token in the buffer?
        Token buffer;       // here is where we keep a Token put back using putback()
    };
    Code:
    Token get();        // get a Token
    The comment adds absolutely nothing to what can be acquired directly from reading the function's definition. Instead of this redundancy of redundancies you could state how the token is acquired, if it involved some kind of weird process that the user might want to know about for performance reasons for instance.

    Code:
    bool full {false};      // is there a Token in the buffer?
    void putback(Token t);     // put a Token back
    These have once again the same problem as the above function. The 'full' member implies that the token stream can only have a single token in the buffer. That might be worth noting in a bigger scope, like a comment refering to the structure itself, because this becomes an invariant that can break things if someone doesn't pay attention.

    Here are some more plain redundancies:
    Code:
    buffer = t;     // copy t to buffer
    full = true;    // buffer is now full
    double val; cin >> val;     // read a floating-point number
    return t.value;  // return the number's value
    I'm not saying some of these lines do not need comments, those are just not the right ones.

    I am of the opinion that comments should be used rarely. They should be used to justify, explain and put into context a set of actions or a specific structure and not just a single action. A single expression should be "self commenting", explaining itself through the elements it uses. Of course, this can sometimes be hard, especially in constructs like switches, but I still say you should avoid comments inside a function / class as much as possible.
    This is my personal opinion though.

    Despite this, the examples above are simply redundant. They are all pretty much prime examples of how NOT to use comments. The weird thing is that so many people do it, it's not even funny. It's not rocket science to see that if I have a class named X with a member named y, the y is of X. I don't need to go out of my way to say that //y of X, or something like that. And then they justify it on being new. Sure, it takes some sense and experience to write good comments, but justifying this with inexperience is like justifying running backwards in a foot race with inexperience.

    I like how people don't seem to want to waste time with good formating or design, but are full on eager to write comments everywhere.

    There, that's my rant.

  4. #4
    Registered User
    Join Date
    Mar 2015
    Posts
    384
    Stroustrup, the author of the book I'm using (Programming: Principles and Practice Using C++ Second Edition - a C++ book for Beginners) using comments like that to try to teach readers what's going on. I guess I didn't notice the redundancy. My bad. He did say in the book that comments should only be used where there's something that can't be expressed clearly and directly in code. I should've caught the redundancy from that.

    Anyway, there's a function from std_lib_facilities.h that I need to understand so I can use it:
    Code:
    void error(const std::string& s, int i)
    {
        std::ostringstream os;
        os << s <<": " << i;
        error(os.str());
    }
    Also, I've got the calculator program from the book running now, but the book itself is still teaching us it and how further refine and debug it and so on. I'll post the latest version I've got for now on here, for reference.
    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:
                        x
    
              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 "../custom_std_lib_facilities.h"
    
    class Token {
    public:
        char kind;
        double value;
        Token(char ch)
            :kind(ch), value(0) { }
        Token(char ch, double val)
            :kind(ch), value(val) { }
    };
    
    class Token_stream {
    public:
        Token_stream();
        Token get();
        void putback(Token t);
    private:
        bool full {false};
        Token buffer;
    };
    
    const char number = '8';        // t.kind == number means that t is a number Token
    const char quit = 'x';          // 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
    
    Token_stream ts;
    
    void calculate();
    
    double expression();  // read and evaluate a Expression
    
    void clean_up_mess(); // naive attempt
    
    double term();        // read and evaluate a Term
    
    void keep_window_open();
    
    void keep_window_open(std::string s);
    
    void error(const std::string& s);
    
    int main()
    {
        try
        {
             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 'x'. Good luck.\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 << expression() << '\n';
        }
        catch (std::exception& e)
        {
            std::cerr << e.what() << '\n';      // write error message
            clean_up_mess();
        }
    }
    
    void clean_up_mess()        // naive
    {
        while (true)
        {       // skip until we find a print
            Token t = ts.get();
            if (t.kind == print)
            {
                return;
            }
        }
    }
    
    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:
            error("Bad token");
        }
    }
    
    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 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;
            }
        }
    }
    Honestly, I still can't figure at this point where and how to use comments, so any help there would be appreciated. As for further fixes or refinements/revisions being needed, I'm still reading on in the book to see how to fix the code and stuff, so hold up on that for now. The book should have everything I need for that.

    The huge block-comment was something Stroustrup himself provided in the book for readers to use, actually. It's not my idea. That's why there's his name there, plus the dates from when he was working on it.

  5. #5
    Registered User
    Join Date
    May 2010
    Posts
    120
    The huge block-comment was something Stroustrup himself provided in the book for readers to use, actually. It's not my idea. That's why there's his name there, plus the dates from when he was working on it.
    The huge block-comment is a perfect example of how to use comments. That one in particular is perhaps a bit too verbose, and it is of course aimed at the whole program and not just a single class. It might be better as a piece of separate documentation, stating the purpose of the program itself, but I understand this is supposed to be a compact example. The point is, if you write a similar thing for a specific set of functionality, or element of your program (a class for instance), that will put much more context on the table for all the members of the class. From then on, you may feel the need to add similar explanations to specific members or groups of members to clarify how that element deals with its problems, gotchas (which there's little reason not to avoid when using C++), how we might want to use that element, between other things you may feel the need to state.

    Of course, this is all very personal. You can put comments in a lot of places. I do believe strongly in one thing though, and although this is my oppinion, it has served me well:
    You can have a million comments outside functions, but when writting expressions inside functions, code should be self commenting. If you find the need to write a (non-redundant) comment explaining an expression, the elements involved might have some sort of design problem that makes them confusing. Sure, there might be some cases where comments inside a function might be needed (for instance, when dealing with C interfaces), but they should really be avoided. Even in C, I find that despite the limitations of the language, it's not that hard to write self explainable code, but in C++, the language has so many utilities that allow you to express elements of your program naturally and consistently, that you should really strive to make sure you design your classes / functions / everything else in a way that spare you the trouble of using comments each time you use them. Also, keep in mind that I know that something you might know now, you might not know in one month, so self commenting code doesnt mean code that you know the meaning of right now, it means code that, through names and common operators, presents an implied meaning that is natural and right in context.

    Again, this is just my opinion on how to use comments effectively. And again, redundant comments have no place anywhere (at least not in actual code, when you know the language), so before writting a comment just think: "will this actually tell me something that is not already written clearly in code?" Imagine if in math you had to percede every A U B with a comment like //Union of A with B. Seems pretty silly, right?

  6. #6
    Hurry Slowly vart's Avatar
    Join Date
    Oct 2006
    Location
    Rishon LeZion, Israel
    Posts
    6,788
    Quote Originally Posted by shiroaisu View Post
    Even in C, I find that despite the limitations of the language, it's not that hard to write self explainable code, but in C++, the language has so many utilities that allow you to express elements of your program naturally and consistently, that you should really strive to make sure you design your classes / functions / everything else in a way that spare you the trouble of using comments each time you use them.
    It is not very hard to write a code that makes obvious what this code will do without comment.
    Entirely another story is to understand from the code alone without comment - WHY this code does what it does
    All problems in computer science can be solved by another level of indirection,
    except for the problem of too many layers of indirection.
    – David J. Wheeler

  7. #7
    Registered User
    Join Date
    Mar 2015
    Posts
    384
    Thanks for all of that, but you guys still haven't said anything to help me understand this function:
    Code:
    void error(const std::string& s, int i)
    {
        std::ostringstream os;
        os << s <<": " << i;
        error(os.str());
    }
    I have to use it in my code, but I don't fully understand how to actually call it. I mean, do I pass in a number along with the string when I call it, so that the number becomes i inside the function?

  8. #8
    Hurry Slowly vart's Avatar
    Join Date
    Oct 2006
    Location
    Rishon LeZion, Israel
    Posts
    6,788
    I suppose you call it like
    Code:
    error("fopen failed", errno);
    which is translated into the call of error with string containing something like
    "fopen failed: 13"

    So it is just used to concatenate the error string to some error code associated with the error (maybe return value of the function you have call or something like this)
    All problems in computer science can be solved by another level of indirection,
    except for the problem of too many layers of indirection.
    – David J. Wheeler

  9. #9
    Registered User
    Join Date
    Mar 2015
    Posts
    384
    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.

Popular pages Recent additions subscribe to a feed

Similar Threads

  1. Can this basic calculator be simplified? I'm new to programming.
    By Christian Jakob in forum C Programming
    Replies: 7
    Last Post: 02-08-2013, 03:09 PM
  2. Replies: 1
    Last Post: 02-26-2012, 05:35 AM
  3. programming a chemistry calculator. Need help !
    By ivanleong in forum C Programming
    Replies: 5
    Last Post: 02-25-2012, 07:48 PM
  4. Replies: 3
    Last Post: 08-17-2009, 08:25 AM
  5. C++ Programming - stack based calculator program
    By tinkerbelle in forum C++ Programming
    Replies: 9
    Last Post: 10-13-2003, 03:49 PM