अमूर्त सिंटेक्स ट्री के लिए विज़िटर पैटर्न को लागू करना


23

मैं अपनी स्वयं की प्रोग्रामिंग भाषा बनाने की प्रक्रिया में हूं, जो मैं सीखने के उद्देश्यों के लिए करता हूं। मैंने अपनी भाषा के सबसेट के लिए पहले से ही लेक्सर और एक पुनरावर्ती वंश पार्सर लिखा था (मैं वर्तमान में गणितीय अभिव्यक्तियों, जैसे कि + - * /और कोष्ठक का समर्थन करता हूं )। पार्सर मुझे एक सार सिंटेक्स ट्री वापस सौंपता है, जिस पर मैं Evaluateअभिव्यक्ति का परिणाम प्राप्त करने के लिए विधि कहता हूं । सब कुछ ठीक काम करता है। यहाँ मेरी लगभग वर्तमान स्थिति है (C # में कोड उदाहरण, हालांकि यह बहुत अधिक भाषा-अज्ञेयवादी है):

public abstract class Node
{
    public abstract Double Evaluate();
}

public class OperationNode : Node
{
    public Node Left { get; set; }
    private String Operator { get; set; }
    private Node Right { get; set; }

    public Double Evaluate()
    {
        if (Operator == "+")
            return Left.Evaluate() + Right.Evaluate();

        //Same logic for the other operators
    }
}

public class NumberNode : Node
{
    public Double Value { get; set; }

    public Double Evaluate()
    {
        return Value;
    }
}

हालाँकि, मैं ट्री नोड्स से एल्गोरिथ्म को डिकॉप्ल करना चाहूंगा क्योंकि मैं ओपन / क्लोज्ड सिद्धांत लागू करना चाहता हूं इसलिए मुझे हर नोड क्लास को फिर से खोलना नहीं है जब मैं उदाहरण के लिए कोड जनरेशन को लागू करना चाहता हूं। मैंने पढ़ा कि विज़िटर पैटर्न उसके लिए अच्छा है। मुझे इस बात की अच्छी समझ है कि पैटर्न कैसे काम करता है और यह कि डबल डिस्पैच का उपयोग कैसे होता है। लेकिन पेड़ की पुनरावर्ती प्रकृति के कारण, मुझे यकीन नहीं है कि मुझे इसे कैसे देखना चाहिए। यहाँ मेरा विज़िटर कैसा दिखेगा:

public class AstEvaluationVisitor
{
    public void VisitOperation(OperationNode node)
    {
        // Here is where I operate on the operation node.
        // How do I implement this method?
        // OperationNode has two child nodes, which may have other children
        // How do I work the Visitor Pattern around a recursive structure?

        // Should I access children nodes here and call their Accept method so they get visited? 
        // Or should their Accept method be called from their parent's Accept?
    }

    // Other Visit implementation by Node type
}

तो यह मेरी समस्या है। मैं इसे तुरंत निपटना चाहता हूं जबकि मेरी भाषा बाद में बड़ी समस्या से बचने के लिए बहुत अधिक कार्यशीलता का समर्थन नहीं करती है।

मैंने इसे StackOverflow में पोस्ट नहीं किया क्योंकि मैं नहीं चाहता कि आप एक कार्यान्वयन प्रदान करें। मैं केवल यह चाहता हूं कि आप उन विचारों और अवधारणाओं को साझा करें जिन्हें मैं याद कर सकता हूं, और मुझे यह कैसे करना चाहिए।


1
मैं शायद इसके बजाय एक पेड़ गुना लागू करूँगा
jk।

@jk: क्या आप थोड़ा विस्तार से बताएंगे?
मार्को-फिसेट

जवाबों:


10

यह बच्चे के नोड्स और किस क्रम में जाना है, यह तय करना आगंतुक कार्यान्वयन पर निर्भर है। यह आगंतुक पैटर्न का पूरा बिंदु है।

अधिक परिस्थितियों के लिए आगंतुक को अनुकूलित करने के लिए यह (और यह जावा) जैसे जेनरिक का उपयोग करने के लिए सहायक (और काफी सामान्य) है:

public interface ExpressionNodeVisitor<R, P> {
    R visitNumber(NumberNode number, P p);
    R visitBinary(BinaryNode expression, P p);
    // ...
}

और एक acceptविधि इस तरह दिखाई देगी:

public interface ExpressionNode extends Node {
    <R, P> R accept(ExpressionNodeVisitor<R, P> visitor, P p);
    // ...
}

यह आगंतुक को अतिरिक्त मापदंडों को पारित करने और इसके परिणामस्वरूप एक परिणाम प्राप्त करने की अनुमति देता है। तो, अभिव्यक्ति मूल्यांकन इस तरह लागू किया जा सकता है:

public class EvaluatingVisitor
    implements ExpressionNodeVisitor<Double, Void> {
    public Double visitNumber(NumberNode number, Void p) {
        // Parse the number and return it.
        return Double.valueOf(number.getText());
    }
    public Double visitBinary(BinaryNode binary, Void p) {
        switch (binary.getOperator()) {
        case '+':
            return binary.getLeftOperand().accept(this, p)
                + binary.getRightOperand().accept(this, p);
        // More cases for other operators here.
        }
    }
}

acceptविधि पैरामीटर ऊपर के उदाहरण में प्रयोग किया जाता है नहीं, लेकिन सिर्फ मुझे विश्वास है: यह एक है करने के लिए काफी उपयोगी है। उदाहरण के लिए, त्रुटियों की रिपोर्ट करने के लिए यह एक लकड़हारा उदाहरण हो सकता है।


मैंने कुछ इसी तरह का कार्यान्वयन किया और मैं अब तक के परिणाम से बहुत संतुष्ट हूं। धन्यवाद!
18

6

मैंने पहले एक पुनरावर्ती पेड़ पर आगंतुक पैटर्न को लागू किया है।

मेरी विशेष रूप से पुनरावर्ती डेटा संरचना बेहद सरल थी - सिर्फ तीन नोड प्रकार: जेनेरिक नोड, एक आंतरिक नोड जिसमें बच्चे होते हैं, और एक पत्ती नोड जिसमें डेटा होता है। यह बहुत आसान है जितना मैं आपके एएसटी होने की अपेक्षा करता हूं, लेकिन शायद विचार बड़े पैमाने पर हो सकते हैं।

मेरे मामले में मैंने जानबूझकर बच्चों के साथ नोड को स्वीकार करने की अनुमति नहीं दी, अपने बच्चों को स्वीकार करते हैं, या आगंतुक को स्वीकार करते हैं। यह दौरा किया जा रहा नोड के बच्चों को स्वीकार करने के लिए आगंतुक के सही "यात्रा" सदस्य कार्यान्वयन की जिम्मेदारी है। मैंने इस तरह से चुना क्योंकि मैं अलग-अलग आगंतुक कार्यान्वयन की अनुमति देना चाहता था ताकि पेड़ के प्रतिनिधित्व से स्वतंत्र रूप से मुलाक़ात का क्रम तय कर सकें।

एक माध्यमिक लाभ यह है कि मेरे ट्री नोड्स के अंदर विज़िटर पैटर्न की लगभग कोई भी कलाकृतियाँ नहीं हैं - प्रत्येक "स्वीकार" बस आगंतुक को सही ठोस प्रकार के साथ "भेंट" पर बुलाता है। इससे विज़िटिंग लॉजिक का पता लगाना और समझना आसान हो जाता है, यह सभी विज़िटर कार्यान्वयन के अंदर है।

स्पष्टता के लिए मैंने कुछ C ++ - ish pseudocode जोड़ा है। पहले नोड्स:

class INode {
  public:
    virtual void Accept(IVisitor& i_visitor) = 0;
};

class NodeWithChildren : public INode {
  public:
     virtual void Accept(IVisitor& i_visitor) override {
        i_visitor.Visit(*this);
     }
     // Plus interface for getting the children, exercise for the reader ;-)
 };

 class LeafNode : public INode {
   public:
     virtual void Accept(IVisitor& i_visitor) override {
       i_visitor.Visit(*this);
     }
 };

और आगंतुक:

class IVisitor {
  public:
     virtual void Visit(NodeWithChildren& i_node) = 0;
     virtual void Visit(LeafNode& i_node) = 0;
};

class ConcreteVisitor : public IVisitor
  public:
     virtual void Visit(NodeWithChildren& i_node) override {
       // Do something useful, then...
       for(Node * p_child : i_node) {
         child->Accept(*this);
       }
     }

     virtual void Visit(LeafNode& i_node) override {
        // Just do something useful, there are no children.
     }

};

1
के लिए +1 allow different Visitor implementations to be able to decide the order of visitation। बहुत अच्छा विचार।
मार्को-फिसेट

@ मार्को-फ़िसेट एल्गोरिथ्म (विज़िटर) को तब जानना होगा कि डेटा (नोड्स) कैसे संरचित है। यह एल्गोरिथ्म-डेटा पृथक्करण को तोड़ देगा जो आगंतुक पैटर्न देता है।
बी विस्चर्स 12

2
@Bisschers आगंतुक प्रत्येक नोड प्रकार के लिए एक फ़ंक्शन लागू करते हैं, इसलिए यह जानते हैं कि किसी भी समय यह किस नोड पर काम करता है। यह कुछ भी नहीं तोड़ता है।
मार्को-फिसेट

3

आप पुनरावर्ती संरचना के आसपास विज़िटर पैटर्न को उसी तरह से काम करते हैं जिस तरह से आप अपनी पुनरावर्ती संरचना के साथ कुछ और करते हैं: अपनी संरचना में नोड्स पर पुनरावर्ती जाकर।

public class OperationNode
{
    public int SomeProperty { get; set; }
    public List<OperationNode> Children { get; set; }
}

public static void VisitNode(OperationNode node)
{
    ... Visit this node

    foreach(var node in Children)
    {
         VisitNode(node);
    }
}

public static void VisitAllNodes()
{
    VisitNode(rootNode);
}

यह पार्सर्स के लिए विफल हो सकता है अगर भाषा में गहरी नेस्टेड निर्माण हैं - यह भाषा के कॉल स्टैक के स्वतंत्र रूप से एक स्टैक को बनाए रखने के लिए आवश्यक हो सकता है।
पीट किरखम

1
@PeteKirkham: यह एक बहुत गहरा पेड़ होगा।
रॉबर्ट हार्वे

@PeteKirkham क्या मतलब है कि यह विफल हो सकता है? क्या आपका मतलब किसी प्रकार के StackOverflowException या कि अवधारणा के पैमाने पर अच्छा नहीं होगा? फिलहाल मुझे प्रदर्शन की परवाह नहीं है, मैं केवल मनोरंजन और सीखने के लिए ऐसा करता हूं।
मार्को-फिसेट

@ मार्को-फ़िसेट हाँ, आपको स्टैक ओवरफ़्लो अपवाद मिलता है यदि आप कहते हैं, तो आगंतुक के साथ एक बड़ी, गहरी XML फ़ाइल पार्स करने का प्रयास करें। आप अधिकांश प्रोग्रामिंग भाषाओं के लिए इसके साथ भाग लेंगे।
पीट किरखम
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.