c++compiler-errorslinker-errorscode-structure

How can I structure code accordingly (in headers and source files) so I don't get compiler and linker errors?


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;
}

Solution

  • 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.