0
#include "std_lib_facilities.h"

/*
    Simple calculator

    Revision history:

        Facilities added by Bjarne Stroustrup April 2009
            (underscores, assignment, and constants)
        Revised by Bjarne Stroustrup May 2007
        Revised by Bjarne Stroustrup August 2006
        Revised by Bjarne Stroustrup August 2004
        Originally written by Bjarne Stroustrup
            (bs@cs.tamu.edu) Spring 2004.

    This program implements a basic expression calculator.
    Input from cin; output to cout.

    The grammar for input is:

    Calculation:
        Statement
        Print
        Quit
        Calculation Statement
    
    Statement:
        Declaration
        Expression
    
    Declaration:
        "let" Name "=" Expression
        "const" Name "=" Expression

    Print:
        ;

    Quit:
        q 

    Expression:
        Term
        Expression + Term
        Expression - Term
    Term:
        Primary
        Term * Primary
        Term / Primary
        Term % Primary
    Primary:
        Number
        Name
        Name = Expression
        ( Expression )
        - Primary
        + Primary
    Number:
        floating-point-literal
    Name:
        [a-zA-Z][a-zA-Z_0-9]*   // a letter followed by zero or more letters, underscores, and digits
                                // note that I decided not to start a namewith an underscore
                                // just because I consider it ugly)

        Input comes from cin through the Token_stream called ts.
*/

// Note that I updated the grammar; keeping comments up-to-data is part of modifying code

//------------------------------------------------------------------------------

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';    // name token
const char let    = 'L';    // declaration token
const char con    = 'C';    // const declaration token
const string declkey = "let";       // declaration keyword 
const string constkey = "const";    // const keyword
const string prompt  = "> ";
const string result  = "= "; // used to indicate that what follows is a result

//------------------------------------------------------------------------------

class Token {
public:
    char kind;        // what kind of token
    double value;     // for numbers: a value 
    string name;      // for names: name itself
    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)    {}
};

//------------------------------------------------------------------------------

class Token_stream {
public: 
    Token_stream();   // make a Token_stream that reads from cin
    Token get();      // get a Token (get() is defined elsewhere)
    void putback(Token t);    // put a Token back
    void ignore(char c);      // discard tokens up to an including a c
private:
    bool full;        // is there a Token in the buffer?
    Token buffer;     // here is where we keep a Token put back using putback()
};

//------------------------------------------------------------------------------

// The constructor just sets full to indicate that the buffer is empty:
Token_stream::Token_stream()
:full(false), buffer(0)    // no Token in buffer
{
}

//------------------------------------------------------------------------------

// The putback() member function puts its argument back into the Token_stream's buffer:
void Token_stream::putback(Token t)
{
    if (full) error("putback() into a full buffer");
    buffer = t;       // copy t to buffer
    full = true;      // buffer is now full
}

//------------------------------------------------------------------------------

Token Token_stream::get() // read characters from cin and compose a Token
{
    if (full) {         // check if we already have a Token ready
        full=false;
        return buffer;
    }  

    char ch;
    cin >> ch;          // note that >> skips whitespace (space, newline, tab, etc.)

    switch (ch) {
    case quit:
    case print:
    case '(':
    case ')':
    case '+':
    case '-':
    case '*':
    case '/': 
    case '%':
    case '=':
        return Token(ch); // let each character represent itself
    case '.':             // a floating-point literal can 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
    {
        cin.putback(ch);// put digit back into the input stream
        double val;
        cin >> val;     // read a floating-point number
        return Token(number,val);
    }
    default:
        if (isalpha(ch)) {  // start with a letter
            string s;
            s += ch;
            while (cin.get(ch) && (isalpha(ch) || isdigit(ch) || ch=='_')) s+=ch;   // letters digits and underscores
            cin.putback(ch);
            if (s == declkey) return Token(let); // keyword "let"
            if (s == constkey) return Token(con); // keyword "const"
            return Token(name,s);
        }
        error("Bad token");
    }
}

//------------------------------------------------------------------------------

void Token_stream::ignore(char c)
    // c represents the kind of a Token
{
    // first look in buffer:
    if (full && c==buffer.kind) {
        full = false;
        return;
    }
    full = false;

    // now search input:
    char ch = 0;
    while (cin>>ch)
        if (ch==c) return;
}

//------------------------------------------------------------------------------

Token_stream ts;        // provides get() and putback() 

//------------------------------------------------------------------------------

class Variable {
public:
    string name;
    double value;
    bool var;   // variable (true) or constant (false)
    Variable (string n, double v, bool va = true) :name(n), value(v), var(va) { }
};

//------------------------------------------------------------------------------

vector<Variable> var_table;

//------------------------------------------------------------------------------

double get_value(string s)
    // return the value of the Variable names s
{
    for (int i = 0; i<var_table.size(); ++i)
        if (var_table[i].name == s) return var_table[i].value;
    error("get: undefined variable ", s);
}

//------------------------------------------------------------------------------

void set_value(string s, double d)
    // set the Variable named s to d
{
    for (int i = 0; i<var_table.size(); ++i)
        if (var_table[i].name == s) {
            if (var_table[i].var==false) error(s," is a constant");
            var_table[i].value = d;
            return;
        }
    error("set: undefined variable ", s);
}

//------------------------------------------------------------------------------

bool is_declared(string var)
    // is var already in var_table?
{
    for (int i = 0; i<var_table.size(); ++i)
        if (var_table[i].name == var) return true;
    return false;
}

//------------------------------------------------------------------------------

double define_name(string s, double val, bool var=true)
    // add (s,val,var) to var_table
{
    if (is_declared(s)) error(s," declared twice");
    var_table.push_back(Variable(s,val,var));
    return val;
}

//------------------------------------------------------------------------------

double expression();    // declaration so that primary() can call expression()

//------------------------------------------------------------------------------

// deal with numbers and parentheses
double 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;    // return the number's value
    case name:
        {
            Token next = ts.get();
            if (next.kind == '=') { // handle name = expression
                double d = expression();
                set_value(t.name,d);
                return d;
            }
            else {
                ts.putback(next);       // not an assignment: return the value
                return get_value(t.name); // return the variable's value
            }
        }
    case '-':
        return - primary();
    case '+':
        return primary();
    default:
        error("primary expected");
    }
}

//------------------------------------------------------------------------------

// deal with *, /, and %
double term()
{
    double left = primary();
    Token t = ts.get(); // get the next token from token stream

    while(true) {
        switch (t.kind) {
        case '*':
            left *= primary();
            t = ts.get();
            break;
        case '/':
            {    
                double d = primary();
                if (d == 0) error("divide by zero");
                left /= d; 
                t = ts.get();
                break;
            }
        case '%':
            {    
                int i1 = narrow_cast<int>(left);
                int i2 = narrow_cast<int>(term());
                if (i2 == 0) error("%: divide by zero");
                left = i1%i2; 
                t = ts.get();
                break;
            }
        default: 
            ts.putback(t);        // put t back into the token stream
            return left;
        }
    }
}

//------------------------------------------------------------------------------

// deal with + and -
double expression()
{
    double left = term();      // read and evaluate a Term
    Token t = ts.get();        // get the next token from token stream

    while(true) {    
        switch(t.kind) {
        case '+':
            left += term();    // evaluate Term and add
            t = 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 declaration(Token k)
    // handle: name = expression
    // declare a variable called "name" with the initial value "expression"
    // k will be "let" or "con"(stant)
{
    Token t = ts.get();
    if (t.kind != name) error ("name expected in declaration");
    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,k.kind==let);
    return d;
}

//------------------------------------------------------------------------------

double statement()
{
    Token t = ts.get();
    switch (t.kind) {
    case let:
    case con:
        return declaration(t.kind);
    default:
        ts.putback(t);
        return expression();
    }
}

//------------------------------------------------------------------------------

void clean_up_mess()
{ 
    ts.ignore(print);
}

//------------------------------------------------------------------------------

void calculate()
{
    while (cin)
      try {
        cout << prompt;
        Token t = ts.get();
        while (t.kind == print) t=ts.get();    // first discard all "prints"
        if (t.kind == quit) return;        // quit
        ts.putback(t);
        cout << result << statement() << endl;
    }
    catch (exception& e) {
        cerr << e.what() << endl;        // write error message
        clean_up_mess();
    }
}

//------------------------------------------------------------------------------

int main()
try {
    // predefine names:
    define_name("pi",3.1415926535,false);   // these pre-defiend names are constants
    define_name("e",2.7182818284,false);

    calculate();

    keep_window_open();    // cope with Windows console mode
    return 0;
}
catch (exception& e) {
    cerr << e.what() << endl;
    keep_window_open("~~");
    return 1;
}
catch (...) {
    cerr << "exception \n";
    keep_window_open("~~");
    return 2;
}

//------------------------------------------------------------------------------

Iam specifically interested in calculate() function and particularly in ' while (t.kind == print) t=ts.get(); // first discard all "prints" ' and ' cout << result << statement() << endl;' lines? When I run the program and provide an input as'2+2;' it produces an output as:

//output screen
>2+2;
=4

but if i provide an input as '2+2' only then it produces an output as

//output screen 2
>2+2
=

and in the "output screen 2" if i press enter and semicolon after '=' which is produced as output then it shows the desired output i.e. 4.

// like this
>2+2
=;
4
>

The point where Iam confused is why is the output of value returned by statement() being controlled by the print character ';'?

  • Please do not spam tags that are mutually exclusive. – Filburt May 24 '22 at 15:11
  • ok i will edit it out. – Pleasent Blizzard May 24 '22 at 15:12
  • _The point where Iam confused is why is the output of value returned by statement() being controlled by the print character ';'?_ This is how the author of that calculator designed the calculator input language. (Have a look at the comment at top, specifically how `Calculation:` is defined.) And, I'm not sure whether you are aware that this calculator program has its own language -- which has nothing to do with C++ (except that the calculator ([parser](https://en.wikipedia.org/wiki/Recursive_descent_parser)) is written in C++, and well, it's the same author for both). ;-) – Scheff's Cat May 24 '22 at 16:24
  • @Scheff'sCat Yes the comment clearly expalins that, but the code doesnot do justice while explaining it. Its not clear about how the ';' semicolon is associated with the cout statement. Because the last statement of the calculate() function is soley responsible for producing any output and I dont see how it is associated with ; although compiler responds to the semicolon? please help me. – Pleasent Blizzard May 24 '22 at 16:31
  • You should find this out by single step debugging. This is how (I believe / expect) it works after debugging by eyes: After input of `2+2`, the parser made a recursive descent from `calculate()` via `statement()` into `expression()`. The rule in the comment does not describe correctly what is really done in the `expression()` but I know that recursive descent parsers cannot cope with left recursion. Instead it has to be transformed into a right recursion, and this is what the author did: `Expression := Term ([+-] Term)*`. Please, note the `default:` with the `return left;`. – Scheff's Cat May 25 '22 at 05:48
  • TL;DR: The `while (true)` in `expression()` is not left until a token is received which is not `+` or `-`. Hence, you need the `;` (or something else) to bail out. Btw. FYI: I once made something similar (but with a bit more code): [The Tiny Calculator Project](https://stackoverflow.com/a/46965151/7478597) (There, I also mentioned the issue with Recursive Descent Parsers vs. left recursion.) – Scheff's Cat May 25 '22 at 05:52
  • To elaborate: The `Expression` rule is described as `Expression := Expression '+' Term | Expression '-' Term | Term ;`. This can be transformed into `Expression := Term ExpressionRest ;` and `ExpressionRest := '+' Term ExpressionRest | '-' Term ExpressionRest | lambda ;`. Thereby, lambda is the symbol for an empty branch. Both rules are implemented in `expression()`: The first is the call of `term()` and the `while (true)` loop, the second the `while (true)` with the `switch` and the `case`s. The `default: return left;` represents the lambda i.e. if token doesn't match rule is done. – Scheff's Cat May 25 '22 at 06:02
  • Sorry, in the literature, the empty symbol is not called _lambda_ (for "leer") but _epsilon_ (for "empty"). Probably, I remembered the "lambda" from my University lessons (which were held in German). ;-) – Scheff's Cat May 25 '22 at 06:09
  • Thank you very much, i understood the problem. Thanks for spending time and helping me out. @Scheff's Cat – Pleasent Blizzard May 25 '22 at 08:53

0 Answers0