#include #include #include class ASTVisitor; /////////////////////////////////////////////////////////////////////////////// // An abstract syntax tree expresses the syntactic relationships between // elements in some form of a program. For instance, the expression `1 + 2` // would be captured by the tree: // // (+) // / \ // (1) (2) // // In general, all nodes represent some intermediate computation. The leaf // nodes of the tree are constants/literals, and the internal nodes reflect // operations on values. /////////////////////////////////////////////////////////////////////////////// class ASTNode { public: virtual void accept(ASTVisitor& visitor) = 0; }; class Constant : public ASTNode { public: Constant(int64_t value) : value{value} { } void accept(ASTVisitor& visitor) override; int64_t getValue() const noexcept { return value; } private: int64_t value; }; class BinaryOperator : public ASTNode { public: enum Kind : uint8_t { ADD, SUBTRACT, MULTIPLY, DIVIDE }; BinaryOperator(Kind kind, ASTNode& left, ASTNode& right) : kind{kind}, left{left}, right{right} { } void accept(ASTVisitor& visitor) override; // Given the simplicity of these operations, it's possible this should be // a POD instead. Kind getKind() const noexcept { return kind; } ASTNode& getLeft() const noexcept { return left; } ASTNode& getRight() const noexcept { return right; } private: Kind kind; ASTNode& left; ASTNode& right; }; char getOperatorChar(BinaryOperator::Kind kind) { switch (kind) { case BinaryOperator::ADD: return '+'; case BinaryOperator::SUBTRACT: return '-'; case BinaryOperator::MULTIPLY: return '*'; case BinaryOperator::DIVIDE: return '/'; } } /////////////////////////////////////////////////////////////////////////////// // A Visitor based infrastructure can allow for extending the underlying // operations of the abstract syntax trees. First we write the glue that will // bind things together. This includes the core visitor interface as well as // the acceptors for the underlying node kinds. /////////////////////////////////////////////////////////////////////////////// class ASTVisitor { public: virtual void visit(ASTNode& node) = 0; virtual void visit(Constant& constant) = 0; virtual void visit(BinaryOperator& constant) = 0; }; void Constant::accept(ASTVisitor& visitor) { // Note: because the actual type of node is known within the specific // implementation of accept, the appropriate visit method will be invoked. visitor.visit(*this); } void BinaryOperator::accept(ASTVisitor& visitor) { visitor.visit(*this); } /////////////////////////////////////////////////////////////////////////////// // At this point, we can go hogwild in writing new visitors that operate on // the AST in different ways. This type of flexible extension is where the // Visitor pattern is most useful. It allows for the core model to be in one // place (like a library), while behaviors can be developed separately (such // as an application that makes use of a library). /////////////////////////////////////////////////////////////////////////////// class Evaluator : public ASTVisitor { public: void visit(ASTNode& node) override { assert(false && "Invalid node during evaluation"); } // For this visitor, we will always store the result in `result` after a call // to `visit`. void visit(Constant& constant) override { result = constant.getValue(); } void visit(BinaryOperator& op) override { op.getLeft().accept(*this); int64_t left = result; op.getRight().accept(*this); int64_t right = result; switch (op.getKind()) { case BinaryOperator::ADD: result = left + right; break; case BinaryOperator::SUBTRACT: result = left - right; break; case BinaryOperator::MULTIPLY: result = left * right; break; case BinaryOperator::DIVIDE: result = left / right; break; } } int64_t getResult() { return result; } private: int64_t result = 0; }; class PrettyPrinter : public ASTVisitor { public: void visit(ASTNode& node) override { assert(false && "Invalid node during evaluation"); } void visit(Constant& constant) override { std::cout << constant.getValue(); } void visit(BinaryOperator& op) override { char opChar = getOperatorChar(op.getKind()); std::cout << '('; op.getLeft().accept(*this); std::cout << ' ' << opChar << ' '; op.getRight().accept(*this); std::cout << ')'; } }; class RPNPrinter : public ASTVisitor { public: void visit(ASTNode& node) override { assert(false && "Invalid node during evaluation"); } void visit(Constant& constant) override { std::cout << constant.getValue(); } void visit(BinaryOperator& op) override { op.getLeft().accept(*this); std::cout << " "; op.getRight().accept(*this); std::cout << ' ' << getOperatorChar(op.getKind()); } }; int main() { Constant one{1}; Constant two{2}; Constant five{5}; BinaryOperator add(BinaryOperator::ADD, one, two); BinaryOperator mult(BinaryOperator::MULTIPLY, add, five); Evaluator evaluator; mult.accept(evaluator); std::cout << evaluator.getResult() << "\n"; PrettyPrinter pretty; mult.accept(pretty); std::cout << "\n"; RPNPrinter rpn; mult.accept(rpn); std::cout << "\n"; return 0; }