views:

91

answers:

3

I'm programming an interpreter for an experimental programming language (educational, fun,...) So far, everything went well (Tokenizer & Parser), but I'm getting a huge problem with some of the data structures in the part that actually runs the tokenized and parsed code.

My programming language basically has only two types, int and string, and they are represented as C++ strings (std class) and ints

Here is a short version of the data structure that I use to pass values around:

enum DataType
{
  Null,
  Int,
  String
}

class Symbol
{
public:
 string identifier;

 DataType type;
 string stringValue;
 int intValue;
}

I can't use union because string doesn't allow me to.

This structure above is beginning to give me a headache.

I have to scatter code like this everywhere in order to make it work, it is beginning to grow unmaintainable:

if( mySymbol.type == Int )
{
  mySymbol.intValue = 1234;
} else {
  mySymbol.stringValue = "abcde";
}

I use the Symbol data structure for variables, return values for functions and general representation of values in the programming language.

  • Is there any better way to solve this? I hope so!
+4  A: 

What you are doing now is kind of a bastardization of a discriminated union. Problem is you're not using a union, and the functionality for the discriminated union is part of the Symbol class itself.

I suggest two alternatives, in order or preference:

1) Use a variant type. A variant type is like a discriminated union on steroids. One implementation can be found in Boost.

2) Create a proper discriminated union, defined separate from the Symbol class.

EDIT: A discriminated union doesn't actually have to be of type union. It could also be a struct.

John Dibling
+1 for suggesting `Boost.Variant`, also note that it's extremely efficient.
Matthieu M.
+1  A: 

I would probably use inheritance -- define a base class that implements the basic operations you want to support, so most of the other code can just use those. For example:

class value { 
public:
    virtual value &add(value const &other) = 0;
    virtual value &assign(value const &other) = 0;
};

class string_val : public value {
    std::string data;
public:
    string_val &add(string_val const &other)  { data += other.data; return *this; }
    string_val &assign(string_val const &other) { data = other.data; return *this; }
};

Instead of using pure virtuals as I have here, you might prefer for the base class to actually define those functions, but have each throw an exception. These would be invoked only in cases where the derived classes did not provide an overload. This would be used for cases like attempting to divide "xyz" by "abc". With only two derived types this won't save a lot, but the more derived types you might add, the more it (potentially) saves.

Jerry Coffin
As it is, you can't instantiate string_val, since it doesn't override the abstract methods. (I may be mistaken, but wouldn't you need something like double dispatch for this?)
UncleBens
Yes, this was intended as a sketch, not usable code. This basically *is* an implementation of double dispatch.
Jerry Coffin
+2  A: 

The problem comes from the fact, that your symbol class is a type that contains two different types that you are trying to identify through the single type of the class Symbol.

It would be better to polymorphically create the symbols:

class Symbol
{
public:
    virtual Symbol& operator = (int val) = 0; // Pure virtual
    virtual Symbol& operator = (string val) = 0; // Pure virtual
private:
    string identifier;
};

class IntSymbol : public Symbol
{
public:
    virtual Symbol& operator = (int val)
    {
        this->val = val;
        return *this; // to make multiple assignments possible
    }
    virtual Symbol& operator = (string val)
    {
        throw new exception("Programm error");
        return *this; // to make it compile
    }
private:
    int val;
};

You do the same for the StringSymbol

ogni42