1 /**
2 Copyright: Copyright (c) 2017, Joakim Brännström. All rights reserved.
3 License: $(LINK2 http://www.boost.org/LICENSE_1_0.txt, Boost Software License 1.0)
4 Author: Joakim Brännström (joakim.brannstrom@gmx.com)
5 
6 This module contains the basic building blocks for an AST.
7 
8 TODO consider if disabling postblit for visitors is a good recommendation.
9 
10 # Design Goals
11 
12 ## Enable different tree visit algorithms
13 The intension of the design is to separate the algorithm for _how_ to visit the
14 tree from the structural requirements _of_ the tree.
15 
16 This is achieve by the _accept_ function in this module. It correspond with the
17 _accept_ method in the Visitor pattern. It dispatch the call to the composite
18 visitors implAccept. _How_ to visit the specific part of the tree is
19 implemented in implAccept.
20 
21 ## Generic Structural Design
22 It is desired to avoid boilerplate code and dynamic dispatch. This is achieved
23 via compile time introspection in the maybeCallVisit template.
24 
25 A pro side effect of this is that the runtime performance is _probably_ improved.
26 A con side effect is that the compile time may be adversely affected.
27 
28 ## Potential Problem
29 The recommended implementation of using mixins for the implAccept makes it so
30 that when a new node type is added to a subtree all the parents to that part of
31 the tree must update their visitors.
32 
33 This is deemed OK because adding new nodes is assumed to be rare.
34 This design problem do not affect the use override of visit functions.
35 
36 # Usage
37 This is a minimal example using the building blocks of this module.
38 
39 Separate the implAccept in mixin templates to allow reuse of them in a
40 supertree to allow visiting a full tree.
41 ---
42 mixin template NodeA_Accept(VisitorT, UserT) {
43     // to avoid problems of missing imports when using this mixin
44     import my.subtree;
45     void implAccept(ref NodeA n) {
46         static void fallback(ref VisitorT!UserT self, ref UserT user, ref NodeA node) {
47             auto parent_node = cast(Node) node;
48             maybeCallVisit(self, user, parent_node);
49         }
50         foreach (child; n.children) {
51             maybeCallVisit(this, user, child, &fallback);
52         }
53     }
54 }
55 ---
56 
57 Make a subtree visitor for NodeA. Add a convenient visit function for the user
58 to use if the top node isn't of interest in the user implementation. But it is
59 important to still allow an override to be handled correctly.
60 ---
61 struct NodeAVisitor(UserT) {
62     UserT user;
63 
64     void visit(ref NodeA n) {
65         import llvm_hiwrap.ast.tree;
66         static void fallback(ref this self, ref UserT user, ref NodeA node) {
67             accept(n, self);
68         }
69         maybeCallVisit(this, user, n);
70     }
71     mixin NodeA_Accept!(NodeAVisitor, UserT);
72 }
73 ---
74 
75 Make a supertree visitor. Assume that there exist a NodeB with the same
76 implementation as NodeA.
77 ---
78 mixin template TopNode_Accept(VisitorT, UserT) {
79     import my.supertree;
80     void implAccept(ref TopNode n) {
81         static void fallback(T)(ref VisitorT!UserT self, ref UserT user, ref T node) {
82             auto parent_node = cast(Node) node;
83             maybeCallVisit(self, user, parent_node);
84         }
85         foreach (child; n.childrenA) {
86             maybeCallVisit(this, user, child, &fallback!NodeA);
87         }
88         foreach (child; n.childrenB) {
89             maybeCallVisit(this, user, child, &fallback!NodeB);
90         }
91     }
92 }
93 
94 struct SuperVisitor(UserT) {
95     UserT user;
96 
97     void visit(ref TopNode n) {
98         import llvm_hiwrap.ast.tree;
99         static void fallback(ref this self, ref UserT user, ref TopNode node) {
100             accept(n, self);
101         }
102         maybeCallVisit(this, user, n);
103     }
104 
105     mixin NodeA_Accept!(NodeAVisitor, UserT);
106     mixin NodeB_Accept!(NodeAVisitor, UserT);
107     mixin TopNode_Accept!(NodeAVisitor, UserT);
108 }
109 ---
110 */
111 module llvm_hiwrap.ast.tree;
112 
113 /** The accept function that act as a nodes accept method in the Visitor pattern.
114  *
115  * The visitor must implement the implAccept function that correctly visits
116  * the nodes children.
117  *
118  * The algorithm to use when visiting the children is left to the visitor.
119  *
120  * This separates the visitor algorithm (breadth-first/depth-first) from the
121  * structural architect.
122  */
123 void accept(NodeT, SelfT)(ref NodeT node, ref SelfT self) {
124     self.implAccept(node);
125 }
126 
127 /** Helper to dispatch the node if the method exist otherwise to a fallback.
128  *
129  * This implementation with a fallback enables grouping of nodes.
130  * The user can either choose to implement a `visit` method for the specific
131  * node or a `visit` that receive the more generic node.
132  *
133  * Example with a callback:
134  * ---
135  * static void fallback(T)(ref Self s, ref Vis v, T node) {}
136  * maybeCallVisit(this, visitor, node, &fallback!SpecNode);
137  * ---
138  *
139  * Params:
140  *  self = parent visitor that contains visitor
141  *  visitor = the specialized visitor that contain the user impl visit func
142  *  node = the node to act upon
143  *  fallback = visitor do not have an overload taking (node, self).
144  */
145 void maybeCallVisit(SelfT, VisitorT, NodeT, FallbackT)(ref SelfT self,
146         ref VisitorT visitor, ref NodeT node, FallbackT fallback = null) {
147     static if (__traits(compiles, visitor.visit(node, self))) {
148         visitor.visit(node, self);
149     } else static if (!is(FallbackT == typeof(null))) {
150         fallback(self, visitor, node);
151     }
152 }
153 
154 version (unittest) {
155     import unit_threaded : shouldEqual, shouldBeTrue;
156 }
157 
158 @("shall visit a tree of depth zero")
159 unittest {
160     struct A {
161     }
162 
163     struct NullVisitor {
164         void visit(T)(ref T n) {
165             n.accept(this);
166         }
167 
168         void implAccept(ref A) {
169         }
170     }
171 
172     A a;
173     NullVisitor v;
174     v.visit(a);
175 }
176 
177 @("shall visit a tree of depth one")
178 unittest {
179     import std.algorithm;
180 
181     struct A {
182         A[] children;
183     }
184 
185     struct Visitor {
186         void visit(ref A n) {
187             nodesVisited++;
188             n.accept(this);
189         }
190 
191         void implAccept(ref A n) {
192             n.children.each!(a => visit(a));
193         }
194 
195         int nodesVisited;
196     }
197 
198     A a;
199     a.children = [A(), A(), A()];
200     Visitor v;
201 
202     // act
203     v.visit(a);
204 
205     // parent + 3 children
206     v.nodesVisited.shouldEqual(4);
207 }
208 
209 @("shall only call the appropriate visit if it exist")
210 unittest {
211     struct A {
212     }
213 
214     struct B {
215     }
216 
217     struct Visitor {
218         void visit(ref A, ref Visitor self) {
219             called = true;
220         }
221 
222         bool called;
223     }
224 
225     A a;
226     B b;
227     Visitor v;
228 
229     maybeCallVisit(v, v, a);
230     maybeCallVisit(v, v, b);
231 
232     v.called.shouldBeTrue;
233 }