Soyut Bir Sözdizimi Ağacı için Ziyaretçi Şablonunu Uygulama


23

Öğrenme amaçlı yaptığım kendi programlama dilimi oluşturma sürecindeyim. Dilimi alt kümesi için lexer ve özyinelemeli iniş ayrıştırıcıyı zaten yazdım (şu anda + - * /parantez gibi matematiksel ifadeleri destekliyorum ). Ayrıştırıcı bana Evaluate, ifadenin sonucunu elde etmek için yöntemi çağıracağım bir Soyut Sözdizimi Ağacı'nı geri verir. Her şey iyi çalışıyor. Yaklaşık olarak şu anki durumum (C # 'daki kod örnekleri, ancak bu oldukça fazla dil-agnostiği olmasına rağmen):

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

Bununla birlikte, algoritmayı ağaç düğümlerinden ayırmak istiyorum çünkü Açık / Kapalı Prensip uygulamak istiyorum; Ziyaretçi Şablonunun bunun için iyi olduğunu okudum. Desenin nasıl çalıştığını ve çift yönlü gönderimi kullanmanın nasıl bir yol olduğunu iyi anladım. Ancak ağacın özyinelemeli doğası nedeniyle, ona nasıl yaklaşmam gerektiğinden emin değilim. İşte ziyaretçimin neye benzeyeceği:

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
}

Demek bu benim sorunum. Daha sonra daha büyük bir sorun yaşamamak için dilim çok fazla işlevsellik desteklemiyorken hemen ele almak istiyorum.

Bunu StackOverflow'a göndermedim, çünkü bir uygulama yapmanızı istemiyorum. Sadece kaçırmış olabileceğim fikirleri ve kavramları ve buna nasıl yaklaşmam gerektiğini paylaşmanızı istiyorum.


1
Muhtemelen onun yerine bir ağaç kat
uygulardım

jk .: Biraz detay verir misiniz?
marco-fiset

Yanıtlar:


10

Alt düğümleri ziyaret edip etmeme konusunda ve hangi sırayla karar vermek ziyaretçi uygulamasına bağlıdır. Bu, ziyaretçi modelinin asıl amacı budur.

Ziyaretçiyi daha fazla duruma uyarlamak için, bu tür jenerik ilaçları (Java ile) kullanmak faydalıdır (ve oldukça yaygındır):

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

Ve bir acceptyöntem şöyle görünür:

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

Bu, ziyaretçiye ek parametreler iletmesine ve bundan bir sonuç almasına izin verir. Dolayısıyla, ifade değerlendirmesi şöyle uygulanabilir:

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

acceptYöntem parametresi Yukarıdaki örnekte kullanılan, ama sadece bana inan değil: bir tane oldukça yararlıdır. Örneğin, hataları bildirmek için bir Logger örneği olabilir.


Benzer bir şey uyguladım ve sonuçtan çok memnunum. Teşekkürler!
marco-fiset

6

Ziyaretçi kalıbını daha önce özyinelemeli bir ağaç üzerinde uyguladım.

Özel özyinelemeli veri yapım son derece basitti - sadece üç düğüm tipi: genel düğüm, çocuğu olan bir iç düğüm ve veri içeren bir yaprak düğümü. Bu, AST'nizin olmasını beklediğimden çok daha basit, ancak belki de fikirler ölçeklendirebilir.

Benim durumumda kasıtlı olarak çocuklu bir düğümü kabul etmesine izin vermedim çocuklarını kabul et ya da ziyaretçiyi aramasına izin vermedim. Kabul et (çocuk) 'ı kabul et. Görevlendirilen düğümün çocuklarına kabul etmeyi devretmek ziyaretçinin doğru “Ziyaret” üye uygulamasının sorumluluğundadır. Bu yolu seçtim çünkü farklı Ziyaretçi uygulamalarının, ağaç temsilinden bağımsız olarak ziyaret sırasına karar verebilmelerini sağlamak istedim.

İkincil bir fayda ise, ağaç düğümlerimde Ziyaretçi deseninin neredeyse hiçbir eseri olmaması - her "Kabul Et" sadece ziyaretçiye doğru somut tipte "Ziyaret" diyor. Bu, ziyaret mantığını bulmayı ve anlamayı kolaylaştırır, hepsi ziyaretçi uygulamasının içindedir.

Netlik için bazı C ++ - ish sözde kodu ekledim. İlk önce düğümler:

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

Ve ziyaretçi:

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
İçin +1 allow different Visitor implementations to be able to decide the order of visitation. Çok iyi fikir.
marco-fiset

@ marco-fiset Algoritma (ziyaretçi) daha sonra verilerin (düğümlerin) nasıl yapılandırıldığını bilmek zorunda kalacaktır. Bu, ziyaretçi kalıbının verdiği algoritma-veri ayrımını bozacaktır.
B Visschers

2
@BVisschers Ziyaretçiler, her bir düğüm tipi için bir fonksiyon uygular, böylece herhangi bir zamanda hangi düğümde çalıştığını bilir. Hiçbir şeyi kırmaz.
marco-fiset

3

Ziyaretçi desenini özyinelemeli bir yapı etrafında, özyinelemeli yapınızla başka bir şey yaptığınız gibi çalışırsınız: yapınızdaki düğümleri özyinelemeli olarak ziyaret ederek.

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

Dil, derinlemesine yuvalanmış yapılar varsa, ayrıştırıcılar için başarısız olabilir - bir yığının dilin çağrı yığından bağımsız olarak korunması gerekebilir.
Pete Kirkham

1
@PeteKirkham: Bu oldukça derin bir ağaç olmalı.
Robert Harvey,

@PeteKirkham Ne demek başarısız olabilir? Bir çeşit StackOverflowException mı yani konseptin iyi ölçeklenmeyeceğini mi kastediyorsunuz? Şimdilik performansı umursamıyorum, bunu sadece eğlence ve öğrenme için yapıyorum.
marco-fiset

@ marco-fiset Evet, bir ziyaretçiyle büyük, derin bir XML dosyasını ayrıştırmayı denerseniz, yığın taşması istisnası alırsınız. Çoğu programlama dili için onunla kaçacaksınız.
Pete Kirkham
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.