-1

I am currently doing the exercices in Bjarne Stroustup "Programming Principles and Practice Using C++" and I finished the 8th chapter, where he talks about headers and namespaces. Using this knowledge and other online resources, I tired restructuring the Simple Calculator code into multiple header and source files in order to use the calculator function "outside" that file. I am getting 230+ errors and I really don't know why. It's quite a long file, I would highly appreciate anyone that uses their time to look at this. I will provide bellow all code snippets ( quite long ) The project's structure

NOTE: std_lib_facilites.h is provided by the book and contains only simple declarations and functions that eases the understanding of concepts.

calculator.h

#pragma once
void calculate(Token_stream& ts, Symbol_table& st);
double statement(Token_stream& ts, Symbol_table& st);
double declaration(Token_stream& ts, Symbol_table& st);
double square_root(Token_stream& ts, Symbol_table& sts);
double powerf(Token_stream& ts, Symbol_table& st);
double expression(Token_stream& ts, Symbol_table& st);
double term(Token_stream& ts, Symbol_table& st);
double factorial(Token_stream& ts, Symbol_table& st);
double primary(Token_stream& ts, Symbol_table& st);
double variable(Token_stream& ts, Symbol_table& st);
void intro_message();
void cleanup(Token_stream&);

constants.h:

#pragma once
namespace Constants
{
    //Constant declarations and initializations-------------------------------------
    const char number = '8';    //representation of a number type for a Token
    const char sroot = 'S';
    const char let = 'L';       //represents the "let" term in declaration()
    const char name = 'a';      //name token
    const char power = 'P';
    const char vconst = 'C';

    const string decl_key = "let";   //declaration keyword
    const string sroot_key = "sqrt";    //keyword for calling sqare_root() function
    const string power_key = "pow";     //keyword for calling power() function
    const string constant_key = "const";

    const char quit_key = '@';
    const char print_key = ';';
    const char help_key = '$';
    const char show_vars = '&';

    const string prompt = ">>";
    const string result = "=";      //used to indicate that what follows is a result
    const char recover = '~'; //used as an argument for the keep_window_open() functions in catch statements
}

token.h:

#pragma once
class Token {
public:
    char type;
    double value;
    string name;   // used for a Token of type name

    Token(char ch) :type{ ch }, value{ 0 } {};
    Token(char ch, double val) :type{ ch }, value{ val } {};
    Token(char ch, string n) :type{ ch }, name{ n } {};
};

class Token_stream {
public:
    Token_stream();
    Token get();
    void putback(Token t);
    void ignore(char c);

private:
    bool isFull = false;
    Token buffer;
};

variable.h:

#pragma once
class Variable
{
public:
    string name;
    double value;
    bool isConst;

    Variable(string st, double v, bool b) : name{ st }, value{ v }, isConst{ b } {}
    Variable(string st, double v) : name{ st }, value{ v }, isConst{ false } {}
};

class Symbol_table
{
public:
    double get_value(string s);
    void set_value(string s, double n);
    bool is_declared(string var);
    double define_variable(string var, double val, bool isConst);
    void show_variables();

private:
    vector <Variable> var_table;
};

calculator.cpp:

#include "calculator.h"
#include "token.h"
#include "variable.h"
#include "constants.h"
#include "std_lib_facilities.h"
//Grammar implementation---------------------------------------------------------
using namespace Constants;

void calculate(Token_stream& ts, Symbol_table& st)
{
    //double val;
    while (cin)
        try {
        cout << prompt;
        Token t = ts.get();

        while (t.type == print_key) t = ts.get();      // "eat" print_key characters
        if (t.type == quit_key) return;                //NOTE : In void functions, you can do an empty return. 
        if (t.type == help_key) intro_message();
        if (t.type == show_vars) st.show_variables();
        else {
            ts.putback(t);
            cout << result << statement(ts,st) << "\n\n";
        }

        //ts.putback(t);
        //cout << result << statement() << "\n\n";
        //val = statement();
    }
    catch (exception& e)
    {
        cout.clear();
        cerr << "error: " << e.what() << "\n";
        cerr << "Enter " << recover << " to continue.\n";
        cleanup(ts);
    }
    catch (...)
    {
        cerr << "Unknown error.\n";
        cerr << "Enter " << recover << " to continue.\n";
    }
}

double statement(Token_stream& ts, Symbol_table& st)
{
    Token t = ts.get();
    switch (t.type)
    {
    case let:
        return declaration(ts, st);

    default:
        ts.putback(t);
        return expression(ts,st);
    }
}

double declaration(Token_stream& ts, Symbol_table& st)
{
    // assume we already saw "let" (in statement())
    // handle: name = expression
    // declare a variable called "name" with initial value "expression"

    Token t = ts.get();
    bool isConst = false;
    if (t.type == vconst)
    {
        t = ts.get();
        isConst = true;
        if (t.type != name) error("name expected in declaration");
        string var_name = t.name;
    }

    else if (t.type != name) error("name expected in declaration");
    string var_name = t.name;

    Token t2 = ts.get();
    if (t2.type != '=') error("= missing in declaration of ", var_name);

    double d = expression(ts,st);
    st.define_variable(var_name, d, isConst);
    return d;
}

double square_root(Token_stream& ts, Symbol_table& st)
{
    // get a token, assuming that we've already used the string "sqrt" in get()
    Token t = ts.get();
    if (t.type != '(') error("sqrt: '(' expected");
    double e = expression(ts,st);
    if (e < 0) error("sqrt: cannot calculate square root of negative number");

    t = ts.get();
    if (t.type != ')') error("sqrt: ')' expected");
    return sqrt(e);
}

double powerf(Token_stream& ts, Symbol_table& st)
{
    Token t = ts.get();
    if (t.type != '(') error("power: '(' expected");

    double t1 = expression(ts,st);

    t = ts.get();
    if (t.type != ',') error("power: arguments must be separated by a ','");

    double t2 = expression(ts, st);
    if (t2 < 0) error("power: negative exponent");

    t = ts.get();
    if (t.type != ')') error("power: ')' expected");
    return pow(t1, t2);
}

double expression(Token_stream& ts, Symbol_table& st)
{
    double left = term(ts, st);
    Token t = ts.get();

    while (true)
    {
        switch (t.type)
        {

        case '+':
            left += term(ts, st);
            t = ts.get();
            break;

        case '-':
            left -= term(ts, st);
            t = ts.get();
            break;

        default:
            ts.putback(t);
            return left; // if there's no more + and -, return the result
        }
    }
    return left;
}

double term(Token_stream& ts, Symbol_table& st)
{
    double left = factorial(ts, st);
    Token t = ts.get();

    while (true)
    {
        switch (t.type)
        {
            //ou de paste :)
        case '*':
            left *= factorial(ts, st);
            t = ts.get();
            break;

        case '/':
        {
            double d = factorial(ts, st);
            if (d == 0) error("term: division: cannot divide by 0");
            left /= d;
            t = ts.get();
            break;
        }

        case '%': //Only works for integers
        {

            int i1 = narrow_cast<int>(left);
            int i2 = narrow_cast<int>(factorial(ts, st));

            if (i2 == 0) error("term: modulo: cannot divide by 0");

            left = i1 % i2;
            t = ts.get();
            break;
        }

        default:
            ts.putback(t);
            return left;
        }
    }
    return left;
}

double factorial(Token_stream& ts, Symbol_table& st)
{
    double left = primary(ts, st);
    Token t = ts.get();

    switch (t.type)
    {
    case '!':
    {
        int lcopy = narrow_cast<int>(left);
        if (left == 0) return 1; // 0! = 1
        if (left < 0) error("factorial: cannot calculate factorial of a negative number");
        while (lcopy > 1)
        {
            --lcopy;
            left *= lcopy;
        }
        t = ts.get();
        if (t.type == '!') error("factorial: unexpected '!' operator");

    }
    default:
        ts.putback(t);
        return left;
    }

    return left;
}

double primary(Token_stream& ts, Symbol_table& st)
{
    Token t = ts.get();

    switch (t.type)
    {
    case '(':
    {
        double e = expression(ts, st);
        t = ts.get();
        if (t.type != ')') error("primary: ')' expected");
        return e;
    }

    case '{':
    {
        double e = expression(ts, st);
        Token b = ts.get();
        if (b.type != '}') error("primary: '}' expected");
        return e;
    }

    case '-':
        return -primary(ts, st);

    case '+':
        return primary(ts, st);

    case number:
        return t.value;

    case name:
        ts.putback(t);
        return variable(ts, st);

    case power:
        return powerf(ts, st);

    case sroot:
        return square_root(ts, st);

    default:
        error("primary expexted");
    }
}

double variable(Token_stream& ts, Symbol_table& st) {
    Token t = ts.get();
    switch (t.type)
    {
    case name:
    {
        Token t2 = t;
        t = ts.get();
        // check to see if it's an assignment or just a usage of the variable
        if (t.type == '=')
        {
            double e = expression(ts, st);
            st.set_value(t2.name, e);
            return e;
        }
        else
        {
            ts.putback(t);
            return st.get_value(t2.name);
        }
    }
    }
}
//-------------------------------------------------------------------------------

//Additional functions-----------------------------------------------------------
void intro_message() //print a custom "banner"
{
    cout << "---------------------------------------\n"
        << "|Simple calculator - V1.0             |\n"
        << "|                             by BIBAN|\n"
        << "---------------------------------------\n\n"
        << "Supported operators : +, -, *, /, % (for ints only), (), !-factorial\n"
        << "Supported functions :\n"
        << "   - sqrt(expression) - calculates square root of any expression\n"
        << "   - pow(base, exponent) - calculate a base at the power of exponent\n"
        << "      --> base and exponent are expressions\n\n"
        << "Variables can be defined and used as expressions:\n"
        << "   - let variable_name = value - define a variable\n"
        << "   - let const constant_name = value - define a constant\n"
        << "   - variable_name = new_value - assign a new value to a non-constant variable\n"
        << "   - " << show_vars << " - display all variables\n\n"
        << "Use " << quit_key << " to quit the program, " << print_key << " to end an ecuation and " << help_key << " to display this message.\n"
        << "If an error occurs, type in " << recover << " to continue.\n\n";
}

void cleanup(Token_stream& ts)
{ //recover from an error
    ts.ignore(recover);
}
//-------------------------------------------------------------------------------

token.cpp:

#include "token.h"
#include "constants.h"
#include "std_lib_facilities.h"

using namespace Constants;

Token_stream() :isFull(false), buffer(0) {}

Token Token_stream::get()
{
    if (isFull)
    {
        isFull = false;
        return buffer;
    }
    else
    {
        char ch;
        cin >> ch;
        switch (ch)
        {
        case '+':
        case '-':
        case '!':
        case '*':
        case '/':
        case '%':
        case '{':
        case '}':
        case '(':
        case ')':
        case '=':  //for Variable declaration and assignment
        case ',':  //used for separating arguments in functions
        case quit_key:
        case print_key:
        case help_key:
        case show_vars:
            return Token(ch);

        case '0': case '1': case '2': case '3': case '4':
        case '5': case '6': case '7': case '8': case '9':
            cin.putback(ch);
            double d;
            cin >> d;
            return Token(number, d);

        default:
            //test if the next token is a string and return a Token if it's valid
            if (isalpha(ch)) // is ch a letter ?
            {
                string s;
                s += ch;
                while (cin.get(ch) && (isalpha(ch) || isdigit(ch) || ch == '_')) s += ch;

                cin.putback(ch);

                if (s == decl_key) return Token{ let };
                if (s == sroot_key) return Token{ sroot };
                if (s == power_key) return Token{ power };
                if (s == constant_key) return Token{ vconst };

                return Token{ name,s };  //Token of type name (for Variable) and value s(the name for the Variable)
            }
            runtime_error("Bad token.");
        }
    }
}

void Token_stream::putback(Token t)
{
    if (isFull) runtime_error("buffer already full");
    isFull = true;
    buffer = t;
}

//Used to recover from errors by ignoring all characters, except char c
void Token_stream::ignore(char c) // c represents the type for the Token
{
    // look in buffer
    if (isFull && c == buffer.type)
    {
        isFull = false;
        return;
    }
    isFull = false;

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

variable.cpp:

#include "std_lib_facilities.h"
#include "variable.h"

double Symbol_table::get_value(string s)
{
    // return the value of the Variable named s
    for (const Variable& v : var_table)
        if (v.name == s) return v.value;

    error("get: undefined variable ", s);
}

void Symbol_table::set_value(string s, double n)
{
    // set the variable named s to n
    for (Variable& v : var_table)
    {
        if (v.name == s && v.isConst == false)
        {
            v.value = n;
            return;
        }
        else if (v.name == s && v.isConst) error("set_value: cannot change value of a constant variable");
    }
    error("set: undefined variable ", s);
}

bool Symbol_table::is_declared(string var)
{
    //is var in var_table already?
    for (const Variable& v : var_table)
        if (v.name == var) return true;
    return false;
}

double Symbol_table::define_variable(string var, double val, bool isConst)
{
    // add {var,val,isConst} to var_table
    if (is_declared(var)) error(var, " declared twice.");
    var_table.push_back(Variable{ var,val,isConst });
    return val;
}

void Symbol_table::show_variables()
{
    for (int i = 0; i < var_table.size(); ++i)
        cout << var_table[i].name << " = " << var_table[i].value << "\n";
}

and the main.cpp file, has only a main() function:

#include "calculator.h"
#include "token.h"
#include "variable.h"

Token_stream ts;
Symbol_table st;

int main()
{
    calculate(ts, st);
    return 0;
}
  • 2
    Could you at give at least one example of an actual specific error you're getting? It may well be something like "you've left the `std::` prefix off everything", though. – Nathan Pierson Nov 17 '20 at 06:19
  • 1
    Also, make it into a [mcve]. You don't need all this code to be able to reproduce whatever error it is that you are getting. – Ted Lyngmo Nov 17 '20 at 06:22
  • Also, are the `TokenStream` and `Calculator` functions supposed to be _throwing_ the `error`s they occasionally create? Just declaring one as a local variable doesn't really do very much. – Nathan Pierson Nov 17 '20 at 06:29
  • Errors are thrown by all functions, Calculate handles them by outputing error.what() then continue execution of the program using cleanup(). I will post some error examples asap. I wanted to put all the code so the problem could be fully understood. – PeePeePooPoo Nov 17 '20 at 06:45
  • https://imgur.com/a/96uDyiO here are some of the errors – PeePeePooPoo Nov 17 '20 at 06:54
  • @PeePeePooPoo You can see that visual studio has placed the errors in what is (essentially) a random order, which makes it very hard to know what is the cause of all those errors. – john Nov 17 '20 at 06:56
  • @PeePeePooPoo If you switch to the *output tab* the errors will be in the correct order. Then, find the *first* error there and post that *as text* here, and we can get this sorted out quickly. – john Nov 17 '20 at 06:57
  • @PeePeePooPoo This is just one of those qurtks of Visual Studio, most of the time I look at the output tab, not the error list tab, when I have compiler errors. – john Nov 17 '20 at 06:58
  • @PeePeePooPoo One more point, these are compiler errors, the errors thrown by your functions are runtime errors. That's a completely different thing. – john Nov 17 '20 at 07:00
  • 1>c:\users\me\source\repos\project2\calculator\calculator.h(3): error C2065: 'Token_stream': undeclared identifier. Is this ok? – PeePeePooPoo Nov 17 '20 at 07:00
  • Yes, I know the difference between runtime errors and compile-time errors, but thanks for the explanation. – PeePeePooPoo Nov 17 '20 at 07:01
  • @PeePeePooPoo Unfortunately you've failed to post calculator.h in the code above. Please edit the question to include that. – john Nov 17 '20 at 07:01
  • Done. Sorry for the inconvenience. – PeePeePooPoo Nov 17 '20 at 07:03
  • OK, I can see the problem, I'll compose a proper answer, rather than write it in the comments. – john Nov 17 '20 at 07:05
  • Thanks a lot. Is it ok if I would ask you anything else if any other error occurs with this ? – PeePeePooPoo Nov 17 '20 at 07:06
  • @PeePeePooPoo If you have any more questions (that aren't directly related to this quesiton), you should create a new question. – john Nov 17 '20 at 07:17
  • `variable.h` should `#include ` and `#include ` and you should absolutely not rely on `using namespace std;` - especially not in header files. Use `std::string` and `std::vector` everywhere. – Ted Lyngmo Nov 17 '20 at 07:57

2 Answers2

0

The problem is that at the point where calculator.h is included the compiler doesn't yet know what Token_stream and Symbol_table are. This is an error, the compiler does not look forward in the code to find out what those symbols are, it just emits an error.

Three possible solutions in order from worst to best (IMHO)

  1. Just make sure that you #include "calculator.h" after token.h and variable.h. That way when the compiler gets to calculator.h it knows what Token_stream and Symbol_table are so no error.

  2. Add #include "token.h" and #include "variable.h" to the beginning of calculator.h. That way the compiler is forced to read token.h and variable.h before it reads the rest of calculator.h

  3. Add forward declarations to calculator.h.

Add the code

// forward declarations
class Token_stream;
class Symbol_table;

to the beginning of calculator.h. This tells the compiler that those symbols are the names of classes. And (in this case) that's enough for the compilation to proceed.

john
  • 85,011
  • 4
  • 57
  • 81
  • .1>c:\users\me\source\repos\project2\calculator\variable.h(6): error C3646: 'name': unknown override specifier ok now i'm getting this error. My head's spinning – PeePeePooPoo Nov 17 '20 at 07:19
  • @PeePeePooPoo Well that is essentially the same error. The compiler doesn't know what `string` is. So two changes, add `#include ` to the beginning of variable.h **and** change `string` to `std::string`. – john Nov 17 '20 at 07:21
  • stilll no good. though only 90 errors now. i guess it's better. the first error is the same . – PeePeePooPoo Nov 17 '20 at 07:24
  • @PeePeePooPoo You can't forward declare `std::string` BTW so that solution doesn't work in this case. – john Nov 17 '20 at 07:25
  • @PeePeePooPoo It should have worked, although you have the same problem with `vector` later in ths same file. Are you gettnig the same error or a differnet error? – john Nov 17 '20 at 07:26
  • same error, yes.Ii thought of including std_lib_facilities.h, as it already contained declarations for string and vector. No good. Is there something i'm missing ? – PeePeePooPoo Nov 17 '20 at 07:28
  • Not sure. I've seen std_lib_facilities before, it's a Stroustrup authored file I think. I seem to recall it had some weird stuff in it although I can't remember the details. As an experiment I would try removing it and doing what I suggested. – john Nov 17 '20 at 07:31
  • remove all occurences of that ? (in all the headers?) or just in calculator.h ? – PeePeePooPoo Nov 17 '20 at 07:32
  • @PeePeePooPoo Actually I would leave it, I found a copy online, it's not the cause of this latest problem. – john Nov 17 '20 at 07:33
  • @PeePeePooPoo You get the error in variable.h but what cpp file is compiling when the error occurs? If you look forward a few errors in the list, what's the first cpp file to be mentioned? – john Nov 17 '20 at 07:34
  • WOW OK. added the include in all the headers that required `std::string` and `vector` – PeePeePooPoo Nov 17 '20 at 07:35
  • @PeePeePooPoo Yes, it's essentially the same issue as before, you are using `std_lib_facilities.h` to get your decalrations of `string` and `vector`. So that header file needs to be included in any header file that mentions `string` or `vector`. – john Nov 17 '20 at 07:36
  • `std` same as the rest of the standard library – john Nov 17 '20 at 07:38
  • 1>c:\users\me\source\repos\project2\calculator\variable.h(27): error C2039: 'Vector': is not a member of 'std' It is way better though, the first few headers link and compile, and I am only getting 20 errors – PeePeePooPoo Nov 17 '20 at 07:42
  • `Vector` and `vector` are two different things. `Vector` is the Stroustrup class declared in `std_lib_facilites.h`, it's not in the std namespace. – john Nov 17 '20 at 07:43
  • Hang on a sec, std_lib_facilities has this `#define vector Vector` (I know there was some weird stuff in that file). That code essentially replaces vector with Vector. So any reference in your code to vector is actually a reference to Vector. So given that you must make sure that `std_lib_facilities.h` is the first header file to be compiled. Put it at the beginning of all your cpp files. – john Nov 17 '20 at 07:45
  • That's really really bad. I would exect Stroustrup to do better. – john Nov 17 '20 at 07:47
  • 1>main.obj : error LNK2005: "class Token_stream ts" (?ts@@3VToken_stream@@A) already defined in calculator.obj – PeePeePooPoo Nov 17 '20 at 07:48
  • That's a linker error. but I don't see why. `ts` is declared in `main.cpp` but I can't see any declaration of it in `calculator.cpp` in the code you've posted. – john Nov 17 '20 at 07:54
  • IT COMPILED ! One more question: where would you recommend declaring ts and st: inside main or inside calculator.h ? – PeePeePooPoo Nov 17 '20 at 07:56
  • it was my bad that i acually undid something after i posted the code and declarations of ts and st were in calculator.h – PeePeePooPoo Nov 17 '20 at 07:57
  • can't thank you enough dude. you actually took your time and babysitted me. – PeePeePooPoo Nov 17 '20 at 07:58
  • @PeePeePooPoo No problem, the C++ compilation model is tricky until you get used to it. Global variables like `ts` and `st` should be **defined** in a cpp file, but if they are needed in more than one cpp file (not the case for you I think) then they should also be **declared** in a header file. The difference is another thing to learn. For a global variable `Token_stream ts;` is a definition and `extern Token_stream ts;` is a declaration. Variables can be declared as many times as you like, but there must be only one definition, which is the error you had earlier. – john Nov 17 '20 at 08:32
  • that i know. I saw something about newer C++ standards that they support pakage-like structures. Won't mess with that though, I'll first get through the book. Also, I see that in the book he recomends avoiding global and extern variables . Thanks! – PeePeePooPoo Nov 17 '20 at 08:36
0

Also, to anyone that has linker errors, mostly in Linux, MAKE TRIPLE SURE THAT YOU INCLUDE ALL THE RELATED .cpp FILES IN THE G++ COMMAND