使用具有返回值的访客模式实现AST的最佳方法是什么?

时间:2015-01-30 17:03:16

标签: c++ abstract-syntax-tree visitor

我试图使用访问者模式在C ++中实现一个简单的抽象语法树(AST)。通常访问者模式不处理返回值。但在我的AST中有表达式节点,它们关心其子节点的返回类型和值。例如,我有一个像这样的Node结构:

class AstNode
{
public:
    virtual void accept(AstNodeVisitor&) = 0;

    void addChild(AstNode* child);
    AstNode* left() { return m_left; }
    AstNode* right() { return m_right; }
...

private:
    AstNode* m_left;
    AstNode* m_right;
};

class CompareNode : public AstNode
{
public:
    virtual void accept(AstNodeVisitor& v)
    {
        v->visitCompareNode(this);
    }

    bool eval(bool lhs, bool rhs) const
    {
        return lhs && rhs;
    }
};

class SumNode : public AstNode
{
public:
    virtual void accept(AstNodeVisitor& v)
    {
        v->visitSumNode(this);
    }

    int eval(int lhs, int rhs) const
    {
        return lhs + rhs;
    }
};

class AstNodeVisitor
{
public:
    ...
    bool visitCompareNode(CompareNode& node)
    {
        // won't work, because accept return void!
        bool lhs = node.left()->accept(*this);
        bool rhs = node.right()->accept(*this);
        return node.eval(lhs, rhs);
    }

    int visitSumNode(Node& node)
    {
        // won't work, because accept return void!
        int lhs = node.left()->accept(*this);
        int rhs = node.right()->accept(*this);
        return node.eval(lhs, rhs);
    }
};

在这种情况下,CompareNode和SumNode都是二元运算符,但它们依赖于子进程的返回类型。

据我所知,只有两个选项:

  1. accept仍然可以返回void,将返回值保存在传递给每个accept和visit函数的上下文对象中,并在visit函数中使用它们,我知道要使用哪种类型。这应该有效但感觉就像是黑客。

  2. 使AstNode成为一个模板,并接受一个非虚拟的函数,但返回类型取决于模板参数T.但是如果我这样做,我不再拥有一个共同的AstNode *类,并且不能保存任何儿童名单中的AstNode *。

  3. 例如:

    template <typename T`>
    class AstNode
    {
    public:
        T accept(AstNodeVisitor&);
        ...
    };
    

    那么有更优雅的方法吗?对于实施AST步​​行的人来说,这应该是一个相当普遍的问题,所以我想知道什么是最好的做法。

    感谢。

3 个答案:

答案 0 :(得分:3)

访客可以拥有可用于存储结果的成员,例如:

class AstNodeVisitor
{
public:
    void visitCompareNode(CompareNode& node)
    {
        node.left()->accept(*this); // modify b
        bool lhs = b;
        node.right()->accept(*this); // modify b
        bool rhs = b;
        b = node.eval(lhs, rhs);
    }

    void visitSumNode(Node& node)
    {
        node.left()->accept(*this); // modify n
        int lhs = n;
        node.right()->accept(*this);  // modify n
        int rhs = n;
        n = node.eval(lhs, rhs);
    }
private:
    bool b;
    int n;
};

您可能还想保存上次结果的类型或使用boost::variant等内容。

答案 1 :(得分:3)

template<class T> struct tag { using type=T; };
template<class...Ts> struct types { using type=types; }

template<class T>
struct AstVisitable {
  virtual boost::optional<T> accept( tag<T>, AstNodeVisitor&v ) = 0;
  virtual ~AstVisitable() {};
};
template<>
struct AstVisitable<void> {
  virtual void accept( tag<void>, AstNodeVisitor&v ) = 0;
  virtual ~AstVisitable() {};
};
template<class Types>
struct AstVisitables;
template<>
struct AstVisibables<types<>> {
  virtual ~AstVisitables() {};
};
template<class T0, class...Ts>
struct AstVisitables<types<T0, Ts...>>:
  virtual AstVisitable<T0>,
  AstVisitables<types<Ts...>>
{
  using AstVisitable<T0>::accept;
  using AstVisitables<types<Ts...>>::accept;
};

using supported_ast_return_types = types<int, bool, std::string, void>;

class AstNode:public AstVisitables<supported_ast_return_types> {
public:
  void addChild(AstNode* child);
  AstNode* left() { return m_left.get(); }
  AstNode* right() { return m_right.get(); }

private:
  std::unique_ptr<AstNode> m_left;
  std::unique_ptr<AstNode> m_right;
};

template<class types>
struct AstVisiablesFailAll;
template<>
struct AstVisiablesFailAll<> {
  virtual ~AstVisiablesFailAll() {};
};
template<class T>
struct AstVisitableFailure : virtual AstVisitable<T> {
  boost::optional<T> accept( tag<T>, AstNodeVisitor& ) override {
    return {};
  }
};
template<>
struct AstVisitableFailure<void> : virtual AstVisitable<void> {
  void accept( tag<void>, AstNodeVisitor& ) override {
    return;
  }
};
template<class T0, class...Ts>
struct AstVisitablesFailAll<types<T0, Ts...>>:
  AstVisitableFailure<T0>,
  AstVisitableFailAll<types<Ts...>>
{
  using AstVisitableFailure<T0>::accept;
  using AstVisitableFailAll<types<Ts...>>::accept;
};

现在您可以boost::optional<bool> lhs = node.left()->accept( tag<bool>, *this );,并从lhs状态知道是否可以在bool上下文中评估左侧节点。

SumNode看起来像这样:

class SumNode :
  public AstNode,
  AstVisiablesFailAll<supported_ast_return_types>
{
public:
  void accept(tag<void>, AstNodeVisitor& v) override
  {
    accept(tag<int>, v );
  }
  boost::optional<int> accept(tag<int>, AstNodeVisitor& v) override
  {
    return v->visitSumNode(this);
  }
  int eval(int lhs, int rhs) const {
    return lhs + rhs;
  }
};

visitSumNode

boost::optional<int> visitSumNode(Node& node) {
    // won't work, because accept return void!
    boost::optional<int> lhs = node.left()->accept(tag<int>, *this);
    boost::optional<int> rhs = node.right()->accept(tag<int>, *this);
    if (!lhs || !rhs) return {};
    return node.eval(*lhs, *rhs);
}

以上假设在a+b上下文中访问void是可以接受的(如在C / C ++中)。如果不是,那么您需要void访问“无法生成void”的方法。

简而言之,接受需要上下文,这也决定了您期望的类型。失败是一个空的选择。

以上用途boost::optional - std::experimental::optional也可以使用,或者你可以自己动手,或者你可以定义一个穷人的选项:

template<class T>
struct poor_optional {
  bool empty = true;
  T t;
  explicit operator bool() const { return !empty; }
  bool operator!() const { return !*this; }
  T& operator*() { return t; }
  T const& operator*() const { return t; }
  // 9 default special member functions:
  poor_optional() = default;
  poor_optional(poor_optional const&)=default;
  poor_optional(poor_optional const&&)=default;
  poor_optional(poor_optional &&)=default;
  poor_optional(poor_optional &)=default;
  poor_optional& operator=(poor_optional const&)=default;
  poor_optional& operator=(poor_optional const&&)=default;
  poor_optional& operator=(poor_optional &&)=default;
  poor_optional& operator=(poor_optional &)=default;
  template<class...Ts>
  void emplace(Ts&&...ts) {
    t = {std::forward<Ts>(ts)...};
    empty = false;
  }
  template<class...Ts>
  poor_optional( Ts&&... ts ):empty(false), t(std::forward<Ts>(ts)...) {}
};

这很糟糕,因为它构建了T,即使不需要,但它应该有用。

答案 2 :(得分:0)

为了完成起见,我发布了OP提到的模板版本

#include <string>
#include <iostream>

namespace bodhi
{
    template<typename T> class Beignet;
    template<typename T> class Cruller;

    template<typename T> class IPastryVisitor
    {
    public:
        virtual T visitBeignet(Beignet<T>& beignet) = 0;
        virtual T visitCruller(Cruller<T>& cruller) = 0;
    };

    template<typename T> class Pastry
    {
    public:
        virtual T accept(IPastryVisitor<T>& visitor) = 0;
    };

    template<typename T> class Beignet : public Pastry<T>
    {
    public:
        T accept(IPastryVisitor<T>& visitor)
        {
            return visitor.visitBeignet(*this);
        }

        std::string name = "Beignet";
    };

    template<typename T> class Cruller : public Pastry<T>
    {
    public:
        T accept(IPastryVisitor<T>& visitor)
        {
            return visitor.visitCruller(*this);
        }

        std::string name = "Cruller";
    };


    class Confectioner : public IPastryVisitor<std::string>
    {
    public:
        virtual std::string visitBeignet(Beignet<std::string>& beignet) override
        {
            return "I just visited: " + beignet.name;
        }

        virtual std::string visitCruller(Cruller<std::string>& cruller) override
        {
            return "I just visited: " + cruller.name;
        }
    };
}

int main()
{
    bodhi::Confectioner pastryChef;

    bodhi::Beignet<std::string> beignet;
    std::cout << beignet.accept(pastryChef) << "\n";

    bodhi::Cruller<std::string> cruller;
    std::cout << cruller.accept(pastryChef) << "\n";
    return 0;
}

每个糕点都是一个节点,每个访客都可以实现其接受的返回类型。有多个访客可以访问同一个糕点。