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 }