简体   繁体   中英

chapter 6 practice and principles using c++ [drill] using tokens (calculator)

I have come at a stand still on chapter 6 of Bjarne Stroustrup's book Practice and Principles using c++. Basically, I have read this chapter twice and tried to understand the examples about grammars and tokens related to making a calculator. I would say that I understand the examples but when it all comes together, I am not able to debug the code and get the faulty program Bjarne gives us working.

We had to solve 5 errors that causes the program to not compile, and 3 logic errors. I solved 3 of the compile errors, but left 2 of them because I'm not sure if the way I fix them actually helps break the program. I will mark these with

//Syntax error "reason"

on the code. After that, there will be 3 logic errors and I would appreciate any help on figuring these out. You don't have to give me the actual answer for the logic ones, but some clues at least would be appreciated, probably even more so than answers. But if you give me the answer, that would also be great.

I keep looking online to see if anyone else has posted questions about the drill, but so far I haven't come across any. All help is appreciated. Thanks.

This chapter has been giving me a head ache and I would really like to get past it!

Added information:

  1. What I did was declare full to be bool and true and make buffer and char. This is located in the get() function.

  2. in the main function, I declared val a double.

  3. After this, the program will tell me that get() function and primary() function don't return on all paths, so I made the default for get() return a token and the default for primary() return ts.value.

After this, the compiler shows no errors but won't run.

The code below has none of these changes, because I believe my changed help break the program.

// The code 


#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 (get() is defined elsewhere)
void putback(Token t);    // put a Token back
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 get()
{
if (full) {       // do we already have a Token ready?  //Syntax error "full" and "buffer" not declared
    // remove token from buffer
    full=false;
    return buffer;
} 

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

switch (ch) {
case ';':    // for "print"
case 'q':    // for "quit"
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 '9':
    {    
        cin.putback(ch);         // put digit back into the input stream
        double val;
        cin >> val;              // read a floating-point number
        return Token('8',val);   // let '8' represent "a number"
    }
  default:
    error("Bad token");
  }
}

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

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

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

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 '8':            // we use '8' to represent a number
    return t.value;  // return the number's value
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();
    case '/':
        {    
            double d = primary();
            if (d == 0) error("divide by zero");
            left /= d; 
            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
    }
 }
}

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

int main()
try
{
while (cin) {
    Token t = ts.get();

    if (t.kind == 'q') break; // 'q' for quit
    if (t.kind == ';')        // ';' for "print now"
        cout << "=" << val << '\n'; //Syntax error "val" not declared
    else
        ts.putback(t);
    val = expression(); //Syntax error "val" not declared
}
keep_window_open();
}
catch (exception& e) {
    cerr << "error: " << e.what() << '\n'; 
    keep_window_open();
    return 1;
}
catch (...) {
    cerr << "Oops: unknown exception!\n"; 
    keep_window_open();
    return 2;
}

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

As you know, the purpose of the programme is to compute simple arithmetic expressions. The expressions are made of numbers, arithmetic operators (sum, substraction, multiplication, division, remainder), and parentheses to group compounds and override the usual operator precedences.

The programme is built on top of the Token and Token_stream classes, which permit the lexical analysis of the original expression encoded as a stream of characters (the text input by the user). The later class extracts possibly meaningful characters from the underlying stream ( cin ), and depending on their values, build Token instances, tokens with additional semantic (*):

  • parentheses and operators carry their usual meaning
  • digits indicate the beginning of numbers, thus groups of digits are extracted as double

Since the Token class uses a single character to identify its kind , numbers are associated with the character 8 ; note, however, that this is completely arbitrary, any other character not used by another token kind (ie. operators or parentheses here) could be used in its place: char kind might as well be int kind , or better yet enum kind with a well defined enum to represent that value without incidence on the programme (in this simple setting, char happens to work well and is easy to implement).

The code performing the actual expression computation is divided in 3 functions, naturally covering the operator precedence:

  • primary computes number from constants (ie. numbers as they come out of the stream), and parenthetized expressions,
  • term computes operations with higher precedence,
  • expression deals with lower precedence operations

Again, in each case, a double is returned, clearly indicating the type used to represent numbers.

The syntax error:

As explained by the other answer, the val variable must be declared in the scope of its use. You may define that variable either as a double (the type of the computation result), or a string (in which case the value will be converted).

The logic errors:

The first one is located in the number token construction code: look at the characters used to detect if a number is coming next in the stream.

The second one is in the Token_stream::get class method: roughly speaking, its declaration and definition don't match.

The third one looks like a copy and paste error in the expression function: it looks like 2 operations are handled, but do we really compute 2 different output?


(*): Given that >> skips blanks and other "unneeded" character from the stream, we really have two layers of tokening.

if (t.kind == ';')        // ';' for "print now"
    cout << "=" << val << '\n'; //Syntax error "val" not declared
else
    ts.putback(t);
val = expression(); //Syntax error "val" not declared

The compiler does not know what val is all about. You should declare it on top and assign it to a string you wanted to display.

String val = '';
...
val = 'message';

The compilation errors are:

1) In the first line after #include

lass Token  //Wrong
class Token //Correct

2) In the member function get() is declared outside the class definition so the notation is wrong, this generate the error of full and buffer variables

Token get()                 //Wrong
Token Token_stream::get()   //Correct

3) In the primary member function

 if (t.kind != ')') error("')' expected);   //Wrong
 if (t.kind != ')') error("')' expected");  //Correct

4) In the expression member function

double left = term(;    //Wrong
double left = term();   //Correct

5) In the main function the variable val is not declare so you need:

double val=0;

With that corrections the program should be able to compile correctly:

The Logic errors are fun to hunt so the clues are:

  1. Try using the number 8
  2. Use multiplications
  3. Use Subtractions

If you use Visual C++, try to use breakpoints and debug using F11, is easier to find the logic errors

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM