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 modulellvm_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 voidaccept(NodeT, SelfT)(refNodeTnode, refSelfTself) {
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 voidmaybeCallVisit(SelfT, VisitorT, NodeT, FallbackT)(refSelfTself,
146 refVisitorTvisitor, refNodeTnode, FallbackTfallback = null) {
147 staticif (__traits(compiles, visitor.visit(node, self))) {
148 visitor.visit(node, self);
149 } elsestaticif (!is(FallbackT == typeof(null))) {
150 fallback(self, visitor, node);
151 }
152 }
153 154 version (unittest) {
155 importunit_threaded : shouldEqual, shouldBeTrue;
156 }
157 158 @("shall visit a tree of depth zero")
159 unittest {
160 structA {
161 }
162 163 structNullVisitor {
164 voidvisit(T)(refTn) {
165 n.accept(this);
166 }
167 168 voidimplAccept(refA) {
169 }
170 }
171 172 Aa;
173 NullVisitorv;
174 v.visit(a);
175 }
176 177 @("shall visit a tree of depth one")
178 unittest {
179 importstd.algorithm;
180 181 structA {
182 A[] children;
183 }
184 185 structVisitor {
186 voidvisit(refAn) {
187 nodesVisited++;
188 n.accept(this);
189 }
190 191 voidimplAccept(refAn) {
192 n.children.each!(a => visit(a));
193 }
194 195 intnodesVisited;
196 }
197 198 Aa;
199 a.children = [A(), A(), A()];
200 Visitorv;
201 202 // act203 v.visit(a);
204 205 // parent + 3 children206 v.nodesVisited.shouldEqual(4);
207 }
208 209 @("shall only call the appropriate visit if it exist")
210 unittest {
211 structA {
212 }
213 214 structB {
215 }
216 217 structVisitor {
218 voidvisit(refA, refVisitorself) {
219 called = true;
220 }
221 222 boolcalled;
223 }
224 225 Aa;
226 Bb;
227 Visitorv;
228 229 maybeCallVisit(v, v, a);
230 maybeCallVisit(v, v, b);
231 232 v.called.shouldBeTrue;
233 }