2015-01-30 3 views
1

저는 방문자 패턴을 사용하여 C++에서 간단한 추상 구문 트리 (AST)를 구현하려고합니다. 일반적으로 방문자 패턴은 반환 값을 처리하지 않습니다. 하지만 내 AST에는 자식 노드의 반환 형식과 값을 염두에 둔 표현식 노드가 있습니다. 예를 들어, 내가 노드의이 같은 구조를 가지고 : CompareNode 및 SumNode 모두 이항 연산자이다이 경우반환 값이있는 방문자 패턴을 사용하여 AST를 구현하는 가장 좋은 방법은 무엇입니까?

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

을하지만 그들은 자녀의 방문의 반환 형식에 의존하고 있습니다.

지금까지 내가 단지 2 옵션이 있습니다, 그것을 작동하게 볼 수 있습니다 :

  1. 여전히 빈 반환 할 수 있습니다 동의, 컨텍스트 각각에 전달되는 객체에 동의하고 방문의 반환 값을 저장해 함수를 호출하고 방문 함수에서 사용합니다. 여기서 내가 사용할 형식을 알고 있습니다. 이것은 작동하지만 해킹처럼 느껴질 것입니다.

  2. AstNode를 템플릿으로 만들고 함수를 가상으로 만들지는 않지만 반환 형식은 템플릿 매개 변수 T에 달려 있습니다. 이렇게하면 더 이상 일반적인 AstNode * 클래스가없고 AstNode *를 저장할 수 없습니다. 아이들 목록. 예를 들어

:

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

그래서이 일을 더 우아한 방법이? 이것은 AST 보행을 구현하는 사람들에게는 상당히 일반적인 문제 여야하므로 가장 좋은 방법이 무엇인지 알고 싶습니다.

감사합니다.

+0

반환 값의 형식은 무엇입니까? '문자열'? 'int'? '더블 '? –

+0

@RSahu : OP는 반환 값이 공용체 또는 클래스라고 가정해야합니다. 그러면 그는 자신이 좋아하는 것을 반환 할 수 있습니다. –

+0

@RSahu : 반환 값은 형식 및 사용자 정의 형식의 빌드 선택 일 수 있지만 컴파일 타임에 알려져 있습니다. 구체적으로 말하면 반환 값은 bool, void 또는 int, double 등 가능한 모든 숫자 유형을 나타내는 boost :: variant 유형이 될 수 있습니다. – swang

답변

2

방문자가 저장 결과에 사용할 수있는 멤버를 가질 수 있습니다, 뭔가 같은 : 또한 지난 결과의 유형을 저장하거나 boost::variant 같은 것을 사용할 수 있습니다

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

.

+0

결과를 방문자 자체에 저장하는 것이 좋습니다. 그렇게 생각하지 않아. 이것은 작동하지만 IMO 코드는 반환 값을 사용하는 것보다 덜 명확 해 보일 것입니다. – swang

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);을 할 수있는, 왼쪽 노드가 bool 맥락에서 평가 될 수있는 경우 lhs의 상태에서 알고있다. 상기

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

(C/C++에서 같다) void 문맥 a+b 방문하면 받아 들일 수 있다고 가정

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을 :

SumNode은 다음과 같다. 그렇지 않다면 void 방문을위한 "void 생산 실패"가 필요합니다.

요컨대 받아들이는 데 필요한 컨텍스트가 결정되는 컨텍스트가 필요합니다. 실패는 빈 선택적입니다.

위의 사용

boost::optional- std::experimental::optional도 작동 것이다, 또는 당신은 당신의 자신의 롤 수 있습니다, 또는 당신은 가난한 사람의 선택을 정의 할 수 있습니다 : 필요하지 않은 경우 심지어 T을 구성하기 때문에, 짜증

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)...) {} 
}; 

,하지만 그것은 일종의 일을해야합니다.

+0

답변 해 주셔서 감사합니다.하지만 귀하의 코드가 무엇을하고 있는지 파악하기 위해 애 쓰고 있습니다. 여기에 디자인 원리를 설명해 주시겠습니까? – swang

+0

@swang 필자는 태그를 사용하여 리턴해야하는 것을 알려주는'accept' 가상 오버로드 세트를 만들었습니다. 그들은'optional '을 반환하는데, 아무것도 없다는 의미는 "실패하고 그 타입을 생성 할 수 없다"입니다. 그런 다음 sum 노드에서 lhs와 rhs가'int'를 생성 할 것으로 예상하므로'int'를 물어 봅니다. 성공하면 그 합계를 반환합니다. 'AstVisitables'는 타입의 팩을 전달하여 다른'accept' 메소드를 생성합니다. 'AstVisiablesFailAll'은 각 메소드의 디폴트 "실패"버전을 만듭니다. 'SumNode'는'int' 또는'void'를 요구할 때 성공할 수 있습니다. 그래서 그것들을 오버라이드합니다. – Yakk

+0

나는이 일을 이해하고 수정 된 버전을 실행할 수 있었지만 여전히 한 가지 질문이 있습니다. 유형 유형에 유형 = 유형을 사용하는 것이 무엇입니까? – swang

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

모든 과자는 노드와 모든 방문자가 가능 반환 유형을 구현할 수에 의해 설명되어있는 템플릿 버전을 게시 할 수 있습니다. 여러 명의 방문객이 동일한 생과자를 방문 할 수 있습니다.