Triển khai mẫu khách truy cập cho cây cú pháp trừu tượng


23

Tôi đang trong quá trình tạo ngôn ngữ lập trình của riêng mình, điều mà tôi làm cho mục đích học tập. Tôi đã viết lexer và một trình phân tích cú pháp gốc đệ quy cho một tập hợp con ngôn ngữ của tôi (tôi hiện hỗ trợ các biểu thức toán học, chẳng hạn như + - * /và dấu ngoặc đơn). Trình phân tích cú pháp trao lại cho tôi một Cây cú pháp trừu tượng, trên đó tôi gọi Evaluatephương thức để lấy kết quả của biểu thức. Mọi thứ đều hoạt động tốt. Đây là xấp xỉ tình hình hiện tại của tôi (ví dụ về mã trong C #, mặc dù điều này khá nhiều ngôn ngữ không thể biết):

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

Tuy nhiên, tôi muốn tách rời thuật toán khỏi các nút cây vì tôi muốn áp dụng Nguyên tắc Mở / Đóng để tôi không phải mở lại mọi lớp nút khi tôi muốn thực hiện tạo mã chẳng hạn. Tôi đọc rằng Mô hình khách truy cập là tốt cho điều đó. Tôi có một sự hiểu biết tốt về cách thức hoạt động của mô hình và sử dụng công văn kép là cách để đi. Nhưng do tính chất đệ quy của cây, tôi không chắc mình nên tiếp cận nó như thế nào. Đây là những gì Khách truy cập của tôi sẽ trông như thế nào:

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
}

Vì vậy, đây là vấn đề của tôi. Tôi muốn giải quyết nó ngay lập tức trong khi ngôn ngữ của tôi không hỗ trợ nhiều chức năng để tránh gặp vấn đề lớn hơn sau này.

Tôi không đăng bài này lên StackOverflow vì tôi không muốn bạn cung cấp triển khai. Tôi chỉ muốn bạn chia sẻ những ý tưởng và khái niệm mà tôi có thể đã bỏ lỡ, và làm thế nào tôi nên tiếp cận điều này.


1
Thay vào đó, tôi có thể thực hiện việc gấp cây
jk.

@jk.: Bạn có phiền xây dựng một chút không?
marco-fiset

Câu trả lời:


10

Tùy thuộc vào việc triển khai của khách truy cập để quyết định xem có nên truy cập các nút con hay không và theo thứ tự nào. Đó là toàn bộ quan điểm của mẫu khách.

Để điều chỉnh khách truy cập cho nhiều tình huống hơn, thật hữu ích (và khá phổ biến) để sử dụng các tổng quát như thế này (đó là Java):

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

Và một acceptphương pháp sẽ như thế này:

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

Điều này cho phép truyền các tham số bổ sung cho khách truy cập và lấy kết quả từ nó. Vì vậy, việc đánh giá biểu thức có thể được thực hiện như thế này:

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

Các accepttham số phương pháp không được sử dụng trong ví dụ trên, nhưng chỉ cần tin tôi: nó là khá hữu ích để có một. Ví dụ, nó có thể là một ví dụ Logger để báo cáo lỗi.


Tôi đã kết thúc việc thực hiện một cái gì đó tương tự và tôi rất hài lòng với kết quả cho đến nay. Cảm ơn!
marco-fiset

6

Tôi đã thực hiện mô hình khách truy cập trên một cây đệ quy trước đây.

Cấu trúc dữ liệu đệ quy đặc biệt của tôi cực kỳ đơn giản - chỉ có ba loại nút: nút chung, nút bên trong có con và nút lá có dữ liệu. Điều này đơn giản hơn nhiều so với tôi mong muốn AST của bạn sẽ được, nhưng có lẽ các ý tưởng có thể mở rộng.

Trong trường hợp của tôi, tôi đã cố tình không để Chấp nhận một nút có con gọi Chấp nhận trên con của nó hoặc gọi cho khách truy cập.Visit (con) từ bên trong Chấp nhận. Trách nhiệm của việc thực hiện thành viên "Lượt truy cập" chính xác của khách truy cập để ủy quyền Chấp nhận cho trẻ em của nút được truy cập. Tôi đã chọn cách này vì tôi muốn cho phép các triển khai khác nhau của Khách truy cập có thể quyết định thứ tự truy cập độc lập với đại diện của cây.

Một lợi ích thứ hai là hầu như không có tạo tác nào của mẫu Khách truy cập bên trong các nút cây của tôi - mỗi "Chấp nhận" chỉ gọi "Truy cập" trên khách truy cập với loại cụ thể chính xác. Điều này giúp dễ dàng xác định vị trí và hiểu logic truy cập, tất cả nằm trong triển khai của khách truy cập.

Để rõ ràng, tôi đã thêm một số mã giả C ++ - ish. Đầu tiên các nút:

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

Và người truy cập:

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 cho allow different Visitor implementations to be able to decide the order of visitation. Ý tưởng rất tốt
marco-fiset

@ marco-fiset Thuật toán (khách truy cập) sau đó sẽ phải biết cách dữ liệu (các nút) được cấu trúc. Điều này sẽ phá vỡ sự phân tách dữ liệu thuật toán mà mẫu khách truy cập đưa ra.
B Visschers

2
@BVisschers Khách truy cập thực hiện một chức năng cho từng loại nút, vì vậy nó biết nút nào nó hoạt động trên bất kỳ thời điểm nào. Nó không phá vỡ bất cứ điều gì.
marco-fiset

3

Bạn làm việc mô hình khách truy cập xung quanh một cấu trúc đệ quy giống như cách bạn sẽ làm bất cứ điều gì khác với cấu trúc đệ quy của mình: bằng cách truy cập các nút trong cấu trúc của bạn một cách đệ quy.

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

Điều này có thể thất bại đối với các trình phân tích cú pháp nếu ngôn ngữ có các cấu trúc lồng nhau sâu - có thể cần duy trì một ngăn xếp độc lập với ngăn xếp cuộc gọi của ngôn ngữ.
Pete Kirkham

1
@PeteKirkham: Đó sẽ phải là một cái cây khá sâu.
Robert Harvey

@PeteKirkham Ý bạn là gì nó có thể thất bại? Bạn có nghĩa là một số loại StackOverflowException hoặc khái niệm này sẽ không mở rộng tốt? Hiện tại tôi không quan tâm đến hiệu suất, tôi chỉ làm điều này để giải trí và học tập.
marco-fiset

@ marco-fiset Có, bạn nhận được ngoại lệ tràn ngăn xếp nếu bạn nói, hãy thử phân tích một tệp XML lớn, sâu với một khách truy cập. Bạn sẽ thoát khỏi nó cho hầu hết các ngôn ngữ lập trình.
Pete Kirkham
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.