0

I need some advice on my AST design. I'm using a pretty typical AST node system for a simple interpreter.

At this point I'm trying to implement simple constant propagation. This involves transformations of the node tree which has led to some confusion about the best design for my AST, in particular how to access class members from upcasted Node leafs.

class Node {
public:

    Node* parentNode;
    std::vector<Node*> childNodes;
    Node() {}
};


class Var : public Node {
public:

    std::string identfier;
    Var();
    Var(std::string cidentfier) {
        identfier = cidentfier;
    }

};

class Assign : public Node {
public:

    Node* right;
    Var* left;
    
    Assign();
    Assign(Var* cleft, Node* cright) {
        cleft->parentNode = this;
        cright->parentNode = this;
        left = cleft;
        right = cright;
        childNodes.push_back(cleft);
        childNodes.push_back(cright);
    }
};


Suppose I have a assign Node which looks roughly like x = y

Var* leftNode = new Var("x");
Var* rightNode = new Var("y");

Node* assignNode = new AssignNode(leftNode, rightNode); 

Now I want to replace the right side of the assign node (y) with a new Var Node (z), so that my new expression looks like this x = z. However I only have a reference to the (y) Var Node via the made up getNodeToBeReplaced() function.

Node* &rightNode = getNodeToBeReplaced(); //returns y
Node* assignNode = rightNode->parentNode; //returns assign node with x = y

Var* replacementNode = new Var("z");

I need to change the assignNode's right member to replacementNode in order to modify the tree (also change replacementNode's parent to assignNode but that's fairly trivial).

The issue is, what is the best way to access assignNode's right pointer when the assignNode is upcasted so it is a Node object. I do not want to do any kind of expensive downcasting back to Assign*.

*assignNode->right = *replacementNode; // can't be done as assignNode is upcasted to Node*

The only bit of information I can modify is the pointers to the class members stored within the childNodes vector that each Node has.

Therefore this code works fine for this purpose.

Node* &rightNode = getNodeToBeReplaced(); //returns y
Node* assignNode = rightNode->parentNode; //returns assign node with x = y

auto &parentChild = std::find(assignNode->childNodes.begin(), assignNode->childNodes.end(), rightNode);

Var* replacementNode = new Var("z");

*replacementNode->parentNode = *assignNode;
**toBeReplacedParentChild = *replacementNode;

However I'm unsure as to if this is good AST design. If not all the class members are pushed to the childNodes vector then the replacement would not work. Can anyone propose a better solution to modifying and keeping track of child nodes within a AST?

Tom
  • 1,235
  • 9
  • 22
  • Kind of related, I find "tagged unions" (such as [`std::variant`](https://en.cppreference.com/w/cpp/utility/variant)) to be a lot easier to work with for such tree structures than classes with inheritance. "Type checking" is also a lot less expensive for them – UnholySheep Sep 16 '20 at 13:48
  • @UnholySheep Interesting, do you have any examples of them? – Tom Sep 16 '20 at 14:04
  • None in C++ I'm afraid, I have begun preferring languages with support for pattern matching for such things (which makes this very concise and simple) – UnholySheep Sep 16 '20 at 14:12
  • How about cheap downcasting with static_cast? Obviously you cannot access a field in Assign without casting to Assign. – user253751 Sep 16 '20 at 15:20
  • Also if you store each child pointer *twice* then make sure to keep them in sync. (childNode and left/right) – user253751 Sep 16 '20 at 15:20

0 Answers0